package com.ccp.dev.workflow.service;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.ccp.dev.core.basic.bpm.util.FlowableUtil;
import com.ccp.dev.core.basic.consts.SystemConst;
import com.ccp.dev.core.basic.db.datasource.DataSourceUtil;
import com.ccp.dev.core.basic.response.ResultData;
import com.ccp.dev.core.basic.table.TableModel;
import com.ccp.dev.core.basic.util.ContextUtil;
import com.ccp.dev.core.basic.util.ExceptionUtil;
import com.ccp.dev.core.basic.util.PropertyUtil;
import com.ccp.dev.core.basic.util.UUIDUtils;
import com.ccp.dev.core.basic.web.query.QueryFilter;
import com.ccp.dev.core.util.*;
import com.ccp.dev.core.util.log.SysAuditThreadLocalHolder;
import com.ccp.dev.form.consts.FormConstants;
import com.ccp.dev.form.model.*;
import com.ccp.dev.form.service.DataTemplateService;
import com.ccp.dev.form.service.FormDefService;
import com.ccp.dev.form.service.FormTableService;
import com.ccp.dev.system.dao.GlobalTypeDao;
import com.ccp.dev.system.dao.SysOrgDao;
import com.ccp.dev.system.dao.SysTypeKeyDao;
import com.ccp.dev.system.dao.SysUserDao;
import com.ccp.dev.system.model.*;
import com.ccp.dev.system.service.SysFileService;
import com.ccp.dev.system.service.SysUserService;
import com.ccp.dev.workflow.basic.BpmResult;
import com.ccp.dev.workflow.basic.DataType;
import com.ccp.dev.workflow.bpmconst.BpmConst;
import com.ccp.dev.workflow.bpminterface.ISkipCondition;
import com.ccp.dev.workflow.bpmutil.BpmUtil;
import com.ccp.dev.workflow.bpmutil.FormDataUtil;
import com.ccp.dev.workflow.bpmutil.ServiceUtil;
import com.ccp.dev.workflow.dao.*;
import com.ccp.dev.workflow.event.EventUtil;
import com.ccp.dev.workflow.model.*;
import com.ccp.dev.workflow.model.bpm.*;
import com.ccp.dev.workflow.service.bpm.BpmRunLogService;
import com.ccp.dev.workflow.service.bpm.TaskSignDataService;
import com.ccp.dev.workflow.service.bpm.cmd.CheckInclusiveGatewayCmd;
import com.ccp.dev.workflow.service.bpm.cmd.CheckReachableCmd;
import com.ccp.dev.workflow.service.bpm.impl.BpmFlowableService;
import com.ccp.dev.workflow.service.bpm.thread.MessageUtil;
import com.ccp.dev.workflow.service.bpm.thread.TaskThreadService;
import com.ccp.dev.workflow.service.bpm.thread.TaskUserAssignService;
import org.apache.commons.collections4.map.CaseInsensitiveMap;
import org.apache.commons.lang.StringUtils;
import org.flowable.common.engine.impl.identity.Authentication;
import org.flowable.common.engine.impl.interceptor.CommandExecutor;
import org.flowable.engine.RuntimeService;
import org.flowable.engine.TaskService;
import org.flowable.engine.impl.persistence.entity.ExecutionEntity;
import org.flowable.engine.impl.persistence.entity.HistoricActivityInstanceEntity;
import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.task.api.Task;
import org.flowable.task.service.delegate.DelegateTask;
import org.flowable.task.service.impl.persistence.entity.HistoricTaskInstanceEntity;
import org.flowable.task.service.impl.persistence.entity.TaskEntity;
import org.flowable.task.service.impl.persistence.entity.TaskEntityImpl;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.lang.reflect.Method;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 对象功能:流程实例扩展Service类
 *
 * @author HXD1
 * @version 2019-07-18
 */
@Service
public class ProcessRunService {

    @Resource
    private BpmNodeSetService bpmNodeSetService;
    @Resource
    private ActDefModelDao actDefModelDao;
    @Resource
    private TaskUserAssignService taskUserAssignService;
    @Resource
    private FormDefService formDefService;
    @Resource
    private ProcessRunDao processRunDao;
    @Resource
    private ProcessRunService processRunService;
    @Resource
    private FormHandlerService formHandlerService;
    @Resource
    private FormTableService formTableService;
    @Resource
    private RuntimeService runtimeService;
    @Resource
    private BpmRunLogService bpmRunLogService;
    @Resource
    private DataTemplateService dataTemplateService;
    @Resource
    private TaskDao taskDao;
    @Resource
    private BpmService bpmService;
    @Resource
    private TaskService taskService;
    @Resource
    private TaskMessageService taskMessageService;
    @Resource
    private BpmTaskExeService bpmTaskExeService;
    @Resource
    private BpmProTransToService bpmProTransToService;
    @Resource
    private SysUserDao sysUserDao;
    @Resource
    private TaskUserService taskUserService;
    @Resource
    private TaskOpinionService taskOpinionService;
    @Resource
    private TaskOpinionDao taskOpinionDao;
    @Resource
    private SysOrgDao sysOrgDao;
    @Resource
    private BpmBusLinkDao bpmBusLinkDao;
    @Resource
    private BpmBusLinkService bpmBusLinkService;
    @Resource
    private IdentityService identityService;
    @Resource
    private ExecutionStackService executionStackService;
    @Resource
    private BpmFormRunService bpmFormRunService;
    @Resource
    private SysUserService sysUserService;
    @Resource
    private FormHandlerService bpmFormHandlerService;
    @Resource
    private SysTemplateService sysTemplateService;
    @Resource
    private ProcessCopyToService processCopyToService;
    @Resource
    private ProcessCopyToDao processCopyToDao;
    @Resource
    private TaskForkDao taskForkDao;
    @Resource
    private ExecutionDao executionDao;
    @Resource
    private BpmProStatusDao bpmProStatusDao;
    @Resource
    private TaskHistoryDao taskHistoryDao;
    @Resource
    private GlobalTypeDao globalTypeDao;
    @Resource
    private SysTypeKeyDao sysTypeKeyDao;
    @Resource
    private TaskReadDao taskReadDao;
    @Resource
    private BpmDefAuthorizeService bpmDefAuthorizeService;
    @Resource
    private JumpRule jumpRule;
    @Resource
    private ExecutionStackDao executionStackDao;
    @Resource
    private BpmFormRunDao bpmFormRunDao;
    @Resource
    private CommuReceiverService commuReceiverService;
    @Resource
    private TaskSignDataService taskSignDataService;
    @Resource
    private HistoryActivityDao historyActivityDao;
    @Resource
    private BpmPerformanceService bpmPerformanceService;
    @Resource
    private BpmTaskExeDao bpmTaskExeDao;
    @Resource
    private CommuReceiverDao commuReceiverDao;
    @Resource
    private BpmFlowableService bpmFlowableService;
    @Resource
    private BpmProStatusService bpmProStatusService;
    @Resource
    private SysFileService sysFileService;

    public ProcessRunService() {
    }

    /**
     * 流程实例扩展管理列表
     *
     * @param filter 过滤条件
     * @return 流程扩展对象列表
     */

    public List<ProcessRun> getAll(QueryFilter filter) {
        return processRunDao.getAll(filter);
    }


    /**
     * 流程实例删除
     *
     * @param runId 流程运行id
     */
    @Transactional(rollbackFor = Exception.class)
    public void delByRunId(String runId) {

        if (runId.contains(StringPool.COMMA)) {
            String[] idArray = runId.split(StringPool.COMMA);
            for (String id : idArray) {
                //删除BPM_PRO_RUN表数据
                processRunDao.delById(id);
                //删除BPM_PRO_RUN_HIS表数据
                processRunDao.delByRunId(id);
            }
        } else {
            //删除BPM_PRO_RUN表数据
            processRunDao.delById(runId);
            //删除BPM_PRO_RUN_HIS表数据
            processRunDao.delByRunId(runId);
        }
    }

    public void recoverByRunId(String runId){
        String informType = "1";
        String memo = "删除流程自动驳回";
        String resultMsg;
        String operation;
        try {
            // 追回
            resultMsg = redo(runId, informType, memo);
            operation="追回";
            if(resultMsg==null){
                SysAuditThreadLocalHolder.putParamerter("operation", operation);
                SysAuditThreadLocalHolder.putParamerter("fl", true);
            }else{
                SysAuditThreadLocalHolder.putParamerter("operation", operation);
                SysAuditThreadLocalHolder.putParamerter("fl", false);
            }
        } catch (Exception ex) {
            SysAuditThreadLocalHolder.putParamerter("fl", false);
        }
    }

    /**
     * 从processRun中查找，如果没有的话则从历史表查找
     *
     * @param runId 流程运行id
     * @return 流程运行实例
     */
    public ProcessRun getById(String runId) {
        ProcessRun processRun = processRunDao.getById(runId);
        if (processRun == null) {
            processRun = processRunDao.getByHistoryId(runId);
        }
        return processRun;
    }

    /**
     * yaoluyang  当getById查询到的processRun为空的时候，需要走此方法
     *
     * @param runId 流程运行id
     * @return 流程运行实例
     */

    public ProcessRun getByIdHistory(String runId) {
        return processRunDao.getByIdHistory(runId);
    }

    /**
     * 获得历史实例列表
     *
     * @param queryFilter 过滤条件
     * @return 流程运行列表
     */
    public List<ProcessRun> queryAllHistory(QueryFilter queryFilter) {
        return processRunDao.queryAllHistory(queryFilter);
    }

    /**
     * 已办事宜
     *
     * @return getAlreadyMatterList
     */
    public List<ProcessRun> queryAlreadyMatterList(QueryFilter queryFilter) {
        return processRunDao.queryAlreadyMatterList(queryFilter);
    }

    /**
     * 根据审批人id查询处理过的流程任务数据，包括已经结束的流程
     *
     * @param assignee 审批人
     * @return List<ProcessRun>
     */
    public List<ProcessRun> queryMyProcessedMattersList(String assignee) {
        return processRunDao.queryMyProcessedMattersList(assignee);
    }

    /**
     * @return 返回ProcessRun
     */
    public ProcessRun getProcessRunById(String runId) {
        return processRunDao.getProcessRunById(runId);
    }


    /**
     * 获取流程表单明细，
     *
     * @param runId   流程运行id
     * @param ctxPath 请求地址前缀
     * @return 表单明细地址
     */
    public String getFormDetailByRunId(String runId, String ctxPath) {
        ProcessRun processRun = this.getById(runId);

        String businessKey = processRun.getBusinessKey();
        //结束的流程实例
        String formUrl = "";
        String formDefId;
        boolean isExtForm = false;
        String form;
        FormDef formDef = null;

        if (StringUtil.isEmpty(processRun.getFormDefId()) && StringUtil.isNotEmpty(processRun.getBusinessUrl())) {
            isExtForm = true;
            BpmNodeSet bpmNodeSet = bpmNodeSetService.getBySetType(processRun.getProcDefId(), BpmNodeSet.SETTYPE_BPMFORM);
            formUrl = bpmNodeSet.getFormUrl();
        } else {
            formDefId = processRun.getFormDefId();
            formDef = formDefService.getOneById(formDefId);
        }
        if (!isExtForm) {
            form = "/form/dataTemplate/view/detail?alias=" + formDef.getFormKey() + "&id=" + processRun.getBusinessKey() + "&hideBtn=yes";
        } else {
            form = formUrl;
            if (StringUtil.isNotEmpty(businessKey) && StringUtil.isNotEmpty(form)) {
                form = form.replaceFirst(BpmConst.FORM_PK_REGEX, businessKey);
            }
            // 替换主键。
            if (StringUtil.isNotEmpty(formUrl)) {
                if (!formUrl.startsWith(StringPool.PREFIX_URL)) {
                    form = ctxPath + form;
                }
            }
        }
        return form;
    }

    /**
     * 检查流程定义是否有效
     *
     * @param actDefModel 流程模型
     * @throws Exception 异常
     */
    public void checkBpmDefValid(ActDefModel actDefModel) throws Exception {

        if (actDefModel == null) {
            throw new Exception("没有该流程的定义");
        }
        // 流程状态不为启用并且不为测试状态。
        if (!ActDefModel.STATUS_ENABLED.equals(actDefModel.getStatus()) && !ActDefModel.STATUS_TEST.equals(actDefModel.getStatus())) {
            throw new Exception("该流程已经被禁用");
        }
    }

    /**
     * 启动流程。<br>
     *
     * <pre>
     * 步骤：
     * 1.表单数据保存。
     * 2.启动流程。
     * 3.记录流程运行情况。
     * 4.记录流程执行堆栈。
     * 5.根据流程的实例ID，查询任务ID。
     * 6.取得任务Id，并完成该任务。
     * 7.记录流程堆栈。
     * </pre>
     *
     * @param processCmd 流程执行命令实体
     * @return 流程运行对象
     * @throws Exception 异常
     */
    @Transactional(rollbackFor = Exception.class)
    public ProcessRun startProcess(ProcessCmd processCmd) throws Exception {
        // 通过流程key获取流程定义
        ActDefModel actDefModel = getActDefModelByCmd(processCmd);
        // 检查流程定义是否有效。
        checkBpmDefValid(actDefModel);
        // 是否跳过第一个任务节点，当为1 时，启动流程后完成第一个任务。
        Short toFirstNode = actDefModel.getToFirstNode();
        String procDefId = actDefModel.getProcDefId();
        // 获取第一个节点,在多个开始节点的时候才有值
        String startNode = processCmd.getStartNode();
        //开始节点的id
        String nodeId = startNode;
        if (StringUtil.isEmpty(startNode)) {
            nodeId = NodeCache.getStartNode(procDefId).getNodeId();
        }
        // 开始节点表单配置，若没有配置开始节点的表单，则为全局表单配置
        BpmNodeSet bpmNodeSet = getStartBpmNodeSet(nodeId, procDefId);
        SysUser sysUser = (SysUser) ContextUtil.getCurrentUser();
        ProcessRun processRun = processCmd.getProcessRun();
        // 设置跳过第一个节点配置，用户判断代理
        TaskThreadService.setToFirstNode(toFirstNode);
        // 如果第一步跳转，那么设置发起人为任务执行人。
        if (toFirstNode == 1) {
            List<TaskExecutor> excutorList = new ArrayList<>();
            excutorList.add(TaskExecutor.getTaskUser(sysUser.getUserId(), sysUser.getFullName()));
            taskUserAssignService.addNodeUser(nodeId, excutorList);
        }
        // 设置下一步节点和用户以及分发任务的用户
        setThreadTaskUser(processCmd);
        // 在线表单数据处理
        String businessKey = "";
        // 初始流程运行记录。
        if (BeanUtils.isEmpty(processRun)) {
            processRun = initProcessRun(actDefModel, processCmd);
        } else {
            //根据草稿启动流程
            processRun.setCreateTime(new Date());
            businessKey = processRun.getBusinessKey();
        }
        // 处理关联流程实例CMD对象。
        handRelRun(processCmd, processRun);
        // 启动流程后，处理业务表单，外部调用不触发表单处理
        handForm(processCmd, processRun);

        //设置流程标题。
        if (BeanUtils.isEmpty(processCmd.getProcessRun())) {
            //2019-11-14添加，为了在流程标题处添加runId
            processCmd.addVariable("flowRunId", processRun.getRunId());
            String subject = getSubject(actDefModel, processCmd);
            processRun.setSubject(subject);
            processCmd.addVariable(BpmConst.FLOW_RUN_SUBJECT, subject);
        }

        // 调用前置处理器
        if (!processCmd.isSkipPreHandler()) {
            invokeHandler(processCmd, bpmNodeSet, true);
        }
        if (StringUtil.isEmpty(businessKey)) {
            businessKey = processCmd.getBusinessKey();
        }

        SysOrg sysOrg = (SysOrg) ContextUtil.getCurrentOrg();
        // 增加发起人组织流程变量。
        if (sysOrg != null) {
            processCmd.addVariable(BpmConst.START_ORG_ID, sysOrg.getOrgId());
        }
        // 添加主流程ID
        processCmd.addVariable(BpmConst.FLOW_MAIN_PROCDEFID, processRun.getProcDefId());
        // 添加流程runId。
        processCmd.addVariable(BpmConst.FLOW_RUNID, processRun.getRunId());
        // 设置processRun对象属性。
        // 设置processRun业务主键。
        // 生成流程定义标题
        processRun.setBusinessKey(businessKey);
        processRun.setStatus(ProcessRun.STATUS_RUNNING);
        if (sysOrg != null) {
            processRun.setStartOrgId(sysOrg.getOrgId());
            processRun.setStartOrgName(sysOrg.getOrgName());
        }
        // 添加选择的节点
        if (StringUtil.isNotEmpty(startNode)) {
            processRun.setStartNode(startNode);
        }
        // 启动流程
        ProcessInstance processInstance = startWorkFlow(processCmd);
        String processInstanceId = processInstance.getProcessInstanceId();
        processRun.setProcInstId(processInstanceId);

        // 更新或添加流程实例
        if (BeanUtils.isEmpty(processCmd.getProcessRun())) {
            this.add(processRun);
        } else {
            this.update(processRun);
        }

        List<Task> taskList = TaskThreadService.getNewTasks();
        // 初始化执行的堆栈树
        executionStackService.initStack(processInstanceId, taskList, "");
        processCmd.setProcessRun(processRun);
        // 后置处理器
        if (!processCmd.isSkipAfterHandler()) {
            invokeHandler(processCmd, bpmNodeSet, false);
        }

        // 获取下一步的任务并完成跳转。
        if (toFirstNode == 1) {
            handJumpOverFirstNode(processCmd);
        }
        // 添加运行期表单，外部调用时不对表单做处理。
        if (!processCmd.isInvokeExternal()) {
            bpmFormRunService.addFormRun(procDefId, processRun.getRunId(), processInstanceId);
        }
        // 处理信息
        Map<String, String> map = this.getTempByUseType(SysTemplate.USE_TYPE_NOTIFY);
        taskMessageService.notify(TaskThreadService.getNewTasks(), processCmd.getInformType(), processRun.getSubject(), map, processCmd.getVoteContent(), "");

        // 抛出事件
        EventUtil.publishNodeSqlEvent(procDefId, nodeId, BpmNodeSql.ACTION_SUBMIT, (Map) processCmd.getTransientVar("mainData"));

        // 添加到流程运行日志
        String memo = "启动流程:" + processRun.getSubject();
        bpmRunLogService.addRunLog(processRun.getRunId(), BpmRunLog.OPERATOR_TYPE_START, memo);

        // 更新流程历史实例为 开始标记为1。
        if (toFirstNode == 1) {
            historyActivityDao.updateIsStart(processInstanceId, nodeId);
        } else {
            // 增加流程提交的审批历史
            addSubmitOpinion(processRun, processCmd);
        }
        // 处理代理
        handleAgentTaskExe(processCmd);
        // 由于threadLocal总是没有清除，所以手动清除
        TaskThreadService.clearAll();
        return processRun;
    }

    /**
     * 获取流程模型
     *
     * @param processCmd 流程执行命令实体
     * @return 流程模型
     */
    private ActDefModel getActDefModelByCmd(ProcessCmd processCmd) {
        ActDefModel actDefModel = (ActDefModel) processCmd.getTransientVar(BpmConst.BPM_DEF);
        if (actDefModel == null) {
            if (StringUtil.isNotEmpty(processCmd.getModelKey())) {
                actDefModel = actDefModelDao.getMainByModelKey(processCmd.getModelKey());
            } else if (StringUtil.isNotEmpty(processCmd.getProcDefId())) {
                actDefModel = actDefModelDao.getByProcDefId(processCmd.getProcDefId());
            }

            if (actDefModel != null) {
                processCmd.addTransientVar(BpmConst.BPM_DEF, actDefModel);
                //2020-6-3 jyq添加，修改离线表单启动失败问题，由于之前修改触发新流程代码导致此问题
                processCmd.setProcDefId(actDefModel.getProcDefId());
            }
        }

        return actDefModel;

    }

    /**
     * 获取起始节点的BpmNodeSet
     *
     * @param nodeId    节点id
     * @param procDefId 流程定义id
     * @return 流程节点设置
     */
    private BpmNodeSet getStartBpmNodeSet(String nodeId, String procDefId) {
        BpmNodeSet firstBpmNodeSet = bpmNodeSetService.getByProcDefIdNodeIdOrParentProcDefId(nodeId, procDefId, "");
        if (firstBpmNodeSet != null && firstBpmNodeSet.getFormType() != -1) {
            return firstBpmNodeSet;
        }
        return bpmNodeSetService.getBySetType(procDefId, BpmNodeSet.SETTYPE_GLOABLEFORM);
    }

    /**
     * 若下一任务分发任务节点，则指定下一任务的执行人员
     *
     * @param processCmd 流程执行命令实体
     */
    private void setThreadTaskUser(ProcessCmd processCmd) {
        //正常跳转
        if (processCmd.isBack() == 0) {
            //下一步节点
            String[] nodeIds = processCmd.getLastDestTaskIds();
            //下一步节点执行人
            String[] nodeUserIds = processCmd.getLastDestTaskUids();
            // 设置下一步产生任务的执行人员
            if (nodeIds != null && nodeUserIds != null) {
                taskUserAssignService.addNodeUser(nodeIds, nodeUserIds);
            }
        }
        // 设置了任务执行人。
        if (processCmd.getTaskExecutors().size() > 0) {
            taskUserAssignService.setExecutors(processCmd.getTaskExecutors());
        }
        TaskThreadService.setProcessCmd(processCmd);
    }

    /**
     * 初始化ProcessRun.
     *
     * @return 流程运行对象
     */
    private ProcessRun initProcessRun(ActDefModel actDefModel, ProcessCmd cmd) throws Exception {

        ProcessRun processRun = new ProcessRun();
        BpmNodeSet bpmNodeSet = bpmFormRunService.getStartBpmNodeSet(actDefModel.getProcDefId());

        SysUser curUser = (SysUser) ContextUtil.getCurrentUser();
        if (BeanUtils.isNotEmpty(bpmNodeSet)) {
            //如果是在线表单
            if (BpmConst.FORM_TYPE_ONLINE.equals(bpmNodeSet.getFormType())) {
                //根据表单key可以获取表单明细
                FormDef formDef = formDefService.getDefaultVersionByFormKey(bpmNodeSet.getFormKey());
                if (formDef != null) {
                    processRun.setFormDefId(formDef.getFormDefId());
                }
            } else {
                processRun.setBusinessUrl(bpmNodeSet.getFormUrl());
            }
        }

        processRun.setCreator(curUser.getFullName());
        processRun.setCreatorId(curUser.getUserId());
        processRun.setProcDefId(actDefModel.getProcDefId());
        processRun.setModelId(actDefModel.getId());
        processRun.setProcessName(actDefModel.getName());
        processRun.setFlowKey(actDefModel.getModelKey());
        // 流程实例归档后是否允许打印表单
        processRun.setIsPrintForm(actDefModel.getIsPrintForm());
        processRun.setCreateTime(new Date());
        processRun.setStatus(ProcessRun.STATUS_RUNNING);
        if (ActDefModel.STATUS_TEST.equals(actDefModel.getStatus())) {
            processRun.setIsFormal(ProcessRun.TEST_RUNNING);
        } else {
            processRun.setIsFormal(ProcessRun.FORMAL_RUNNING);
        }
        if (BeanUtils.isNotEmpty(actDefModel.getTypeId())) {
            processRun.setTypeId(actDefModel.getTypeId());
        }

        BpmResult bpmResult = (BpmResult) cmd.getTransientVar("bpmResult");
        if (bpmResult != null) {
            if (bpmResult.getDataType().equals(DataType.NUMBER)) {
                processRun.setBusinessKeyNum(new Long(bpmResult.getBusinessKey()));
            } else {
                processRun.setBusinessKey(bpmResult.getBusinessKey());
            }
            processRun.setTableName(bpmResult.getTableName());
        }
        Map<String, Object> variables = cmd.getVariables();

        String startRunId = (String) variables.get("startRunId");
        // 再启动流程时可以从外部传入RUNID.
        if (startRunId != null) {
            processRun.setRunId(startRunId);
        } else {
            processRun.setRunId(UUIDUtils.getUUIDFor32());
        }

        processRun.setRelRunId(cmd.getRelRunId());

        // 判断是否需要增加全局流水号TODO
        boolean rtn = PropertyUtil.getBooleanByAlias(SysProperty.GLOBAL_FLOW_NO);
        if (rtn) {
            if (!identityService.isAliasExisted(SysProperty.GLOBAL_FLOW_NO, "")) {
                String message = "请设置别名为" + SysProperty.GLOBAL_FLOW_NO + "的流水号";
                throw new RuntimeException(message);
            }
            String flowNo = identityService.nextId(SysProperty.GLOBAL_FLOW_NO);
            processRun.setGlobalFlowNo(flowNo);
        }

        return processRun;
    }

    /**
     * 根据cmd 关联实例ID设置业务主键。
     *
     * <pre>
     * 1.判断cmd中是否传入了关联实例ID。
     * 2.没有再判断草稿中是否记录了关联实例ID。
     * 3.根据实例ID取得业务主键。
     * </pre>
     *
     * @param processCmd 流程执行命令实体
     */
    private void handRelRun(ProcessCmd processCmd, ProcessRun processRun) {
        String relRunId = processCmd.getRelRunId();
        // 如果传入为空，则判断草稿中是否存在。
        if (BeanUtils.isZeroEmpty(relRunId)) {
            // 没有草稿直接返回。
            if (processRun == null) {
                return;
            }

            relRunId = processRun.getRelRunId();
        }

        if (BeanUtils.isZeroEmpty(relRunId)) {
            return;
        }

        String businessKey = getBusinessKeyByRelRunId(relRunId);
        processCmd.setBusinessKey(businessKey);
        // 设置关联数据ID。
        processRun.setRelRunId(relRunId);
    }

    /**
     * 处理业务表单数据。
     *
     * @param processCmd 流程执行命令实体
     * @param processRun 流程运行对象
     * @throws Exception 异常
     */
    private void handForm(ProcessCmd processCmd, ProcessRun processRun) throws Exception {
        if (processCmd.isInvokeExternal()) {
            return;
        }
        FormData bpmFormData = handlerFormData(processCmd, processRun, "");
        if (bpmFormData == null) {
            return;
        }

        processRun.setTableName(bpmFormData.getTableName());
        if (bpmFormData.getPkValue() != null) {
            processRun.setPkName(bpmFormData.getPkValue().getName());
            processRun.setDsAlias(bpmFormData.getDsAlias());
        }
    }

    /**
     * 根据关联runId获取对应的业务主键。
     *
     * <pre>
     * 1.根据关联runId获取历史实例对象。
     * 2.则获取业务主键数据。
     * </pre>
     *
     * @param relRunId 关联runId。
     * @return 业务主键。
     */
    public String getBusinessKeyByRelRunId(String relRunId) {
        if (StringUtil.isEmpty(relRunId)) {
            return "";
        }
        ProcessRun processRun = processRunDao.getByHistoryId(relRunId);
        return processRun.getBusinessKey();
    }

    /**
     * 保存草稿
     *
     * @param processCmd 流程执行命令实体
     * @throws Exception 异常
     */
    public void saveDraft(ProcessCmd processCmd) throws Exception {
        String procDefId = processCmd.getProcDefId();
        ActDefModel actDefModel = actDefModelDao.getByProcDefId(procDefId);
        // 开始节点
        String nodeId = "";
        if (!NodeCache.isMultipleFirstNode(procDefId)) {
            nodeId = NodeCache.getStartNode(procDefId).getNodeId();
        }
        BpmNodeSet bpmNodeSet = getStartBpmNodeSet(nodeId, procDefId);
        ProcessRun processRun = initProcessRun(actDefModel, processCmd);
        String businessKey = "";
        // 保存草稿后，处理业务表单，外部调用不触发表单处理
        if (!processCmd.isInvokeExternal()) {
            FormData formData = handlerFormData(processCmd, processRun, "");
            if (formData != null) {
                businessKey = processCmd.getBusinessKey();
                processRun.setTableName(formData.getTableName());
                if (formData.getPkValue() != null) {
                    processRun.setPkName(formData.getPkValue().getName());
                    processRun.setDsAlias(formData.getDsAlias());
                }
                FormDef defaultForm = formDefService.getDefaultVersionByFormKey(bpmNodeSet.getFormKey());
                processRun.setFormDefId(defaultForm.getFormDefId());
            }
        }
        // 调用前置处理器
        if (!processCmd.isSkipPreHandler()) {
            invokeHandler(processCmd, bpmNodeSet, true);
        }
        if (StringUtil.isEmpty(businessKey)) {
            businessKey = processCmd.getBusinessKey();
        }
        String subject = getSubject(actDefModel, processCmd);
        processRun.setBusinessKey(businessKey);
        processRun.setSubject(subject);
        processRun.setStatus(ProcessRun.STATUS_FORM);
        processRun.setCreateTime(new Date());
        processRunDao.add(processRun);
        processRunDao.addHistory((ProcessRun) processRun.clone());
        // 调用前置处理器
        if (!processCmd.isSkipPreHandler()) {
            invokeHandler(processCmd, bpmNodeSet, true);
        }

        // 抛出事件
        EventUtil.publishNodeSqlEvent(procDefId, nodeId, BpmNodeSql.ACTION_SAVE, (Map) processCmd.getTransientVar("mainData"));

        // 添加到流程运行日志
        String memo = "保存草稿:" + subject;
        bpmRunLogService.addRunLog(processRun.getRunId(), BpmRunLog.OPERATOR_TYPE_SAVEFORM, memo);
    }

    /**
     * 处理在线表单数据。
     *
     * @param processRun 流程运行对象
     * @param processCmd 流程执行命令实体
     * @return 表单数据
     * @throws Exception 异常
     */
    public FormData handlerFormData(ProcessCmd processCmd, ProcessRun processRun, String nodeId) throws Exception {
        String json = processCmd.getFormData();

        if (StringUtils.isEmpty(json)) {
            String opinionField = (String) processCmd.getTransientVar(BpmConst.OPINION_FIELD);
            Boolean batComplte = (Boolean) processCmd.getTransientVar("batComplte");
            if (StringUtil.isNotEmpty(opinionField) && batComplte != null && batComplte) {
                FormDef formDef = formDefService.getOneById(processRun.getFormDefId());
                FormData data = formHandlerService.getByKey(formDef.getTableId(), processRun.getBusinessKey(), null, null, false);
                // 把主表数据放到cmd中,主要为了待会抛出的Nodesql事件
                handleData(processCmd, data);
                // 将意见数据放到映射的字段中去。
                handFieldOpinion(processCmd, data);
                handFormData(data, processRun, nodeId);
            }
            return null;
        }

        FormData formData;
        FormTable formTable;
        String businessKey = processCmd.getBusinessKey();
        // 判断节点Id是否为空，为空表示开始节点。
        boolean isStartFlow = StringUtil.isEmpty(nodeId);

        if (isStartFlow && ProcessRun.STATUS_FORM.equals(processRun.getStatus())) {
            String formDefId = processRun.getFormDefId();
            FormDef formDef = formDefService.getOneById(formDefId);
            formTable = formTableService.getByTableId(formDef.getTableId(), FormField.FIELD_NORMAL_HIDDEN);
        } else {
            if (StringUtil.isNotEmpty(processRun.getParentId())) {
                // 作为外部子流程
                Map<String, Object> variables = runtimeService.getVariables(processRun.getProcInstId());
                String parentProcDefId = (String) variables.get(BpmConst.FLOW_PARENT_PROCDEFID);
                formTable = formTableService.getByProcDefIdAndParent(processRun.getProcDefId(), parentProcDefId);
            } else {
                formTable = formTableService.getByProcDefId(processRun.getProcDefId());
            }
        }

        // 有主键的情况,表示数据已经存在。
        if (StringUtil.isNotEmpty(businessKey)) {
            String pkName = formTable.isExtTable() ? formTable.getPkField() : TableModel.PK_COLUMN_NAME;
            boolean isBusExist;
            if (formTable.isExtTable()) {
                if (FormTable.PKTYPE_NUMBER.equals(formTable.getKeyDataType())) {
                    isBusExist = bpmBusLinkDao.checkBusExist(Long.parseLong(businessKey));
                } else {
                    isBusExist = bpmBusLinkDao.checkBusStrExist(businessKey);
                }
            } else {
                isBusExist = bpmBusLinkDao.checkBusStrExist(businessKey);
            }

            PkValue pkValue = new PkValue(pkName, businessKey);
            pkValue.setIsAdd(!isBusExist);
            formData = FormDataUtil.parseJson(json, pkValue, formTable);
        } else {
            formData = FormDataUtil.parseJson(json, null, formTable);
        }
        // 将意见数据放到映射的字段中去。
        handFieldOpinion(processCmd, formData);

        processCmd.putVariables(formData.getVariables());
        // 生成的主键
        PkValue pkValue = formData.getPkValue();
        businessKey = pkValue.getValue().toString();

        String pk = processRun.getBusinessKey();

        processRun.setBusinessKey(businessKey);
        processCmd.setBusinessKey(businessKey);

        // 把主表数据放到cmd中,主要为了待会抛出的Nodesql事件
        // 设置主表数据
        handleData(processCmd, formData);

        // 启动流程。
        if (isStartFlow) {
            // 保存表单数据,存取表单数据
            handFormData(formData, processRun, "");
        } else {
            //2019-12-31 jy添加，目的是在审批过程中不修改表单数据的租户id，始终是发起人的租户id
            formData.getMainFields().remove("tenantid");
            //跳过时
            handFormData(formData, processRun, nodeId);
            if (!processCmd.isSkip()) {
                processRunDao.update(processRun);
            }
            // 业务主键为空的情况，设置流程主键。设置流程变量。
            if (StringUtil.isEmpty(pk)) {
                processCmd.addVariable(BpmConst.FLOW_BUSINESSKEY, businessKey);
            }
        }
        return formData;
    }

    /**
     * 执行前后处理器。 0 代表失败 1代表成功，-1代表不需要执行
     *
     * @param processCmd 流程执行命令实体
     * @param bpmNodeSet 流程设置
     * @param isBefore   是否是前置处理器
     */
    private void invokeHandler(ProcessCmd processCmd, BpmNodeSet bpmNodeSet, boolean isBefore) throws Exception {
        if (bpmNodeSet == null) {
            return;
        }
        String handler;
        if (isBefore) {
            handler = bpmNodeSet.getBeforeHandler();
        } else {
            handler = bpmNodeSet.getAfterHandler();
        }
        if (StringUtil.isEmpty(handler)) {
            return;
        }

        String[] aryHandler = handler.split("[.]");
        if (BeanUtils.isNotEmpty(aryHandler)) {
            String beanId = aryHandler[0];
            String method = aryHandler[1];
            // 触发该Bean下的业务方法
            Object serviceBean = AppUtil.getBean(beanId);
            if (BeanUtils.isNotEmpty(serviceBean)) {
                Method invokeMethod = serviceBean.getClass().getDeclaredMethod(method, ProcessCmd.class);
                invokeMethod.invoke(serviceBean, processCmd);
            }
        }
    }

    /**
     * 获取流程标题。
     *
     * @param actDefModel 流程模型
     * @param processCmd  流程执行命令实体
     * @return 流程标题
     */
    public String getSubject(ActDefModel actDefModel, ProcessCmd processCmd) {
        // 若设置了标题，则直接返回该标题，否则按后台的标题规则返回
        if (StringUtils.isNotEmpty(processCmd.getSubject())) {
            return processCmd.getSubject();
        }

        String rule = actDefModel.getTaskNameRule();
        Map<String, Object> map = new HashMap<>(16);
        map.put("title", actDefModel.getName());
        SysUser user = (SysUser) ContextUtil.getCurrentUser();
        map.put(BpmConst.StartUser, user.getFullName());
        map.put("startDate", TimeUtil.getCurrentDate());
        map.put("startTime", TimeUtil.getCurrentTime());
        map.put("businessKey", processCmd.getBusinessKey());
        map.putAll(processCmd.getVariables());
        rule = BpmUtil.getTitleByRule(rule, map);
        if (ActDefModel.STATUS_TEST.equals(actDefModel.getStatus())) {
            if (StringUtil.isEmpty(actDefModel.getTestStatusTag())) {
                return rule + "-" + ActDefModel.TEST_TAG;
            } else {
                return rule + "-" + actDefModel.getTestStatusTag();
            }
        }
        return rule;
    }

    /**
     * 处理表单数据
     *
     * @param processCmd   流程执行命令实体
     * @param bpmNodeSet   流程节点设置
     * @param processRun   流程运行对象
     * @param parentNodeId 父节点id
     * @throws Exception 异常
     */
    private void handFormData(ProcessCmd processCmd, BpmNodeSet bpmNodeSet, ProcessRun processRun, String parentNodeId) throws Exception {
        if (processCmd.isInvokeExternal()) {
            return;
        }

        String opinionField = bpmNodeSet.getOpinionField();
        Short opinionHtml = bpmNodeSet.getOpinionHtml();
        if (StringUtil.isEmpty(opinionField)) {
            processCmd.addTransientVar(BpmConst.OPINION_FIELD, opinionField);
            processCmd.addTransientVar(BpmConst.OPINION_SUPPORTHTML, opinionHtml);
        }
        FormData formData = handlerFormData(processCmd, processRun, parentNodeId);
        if (BeanUtils.isNotEmpty(formData)) {
            Map<String, String> options = formData.getOptions();
            // 记录意见
            setOpinionByForm(options, processCmd);
        }
    }

    /**
     * 更新意见。
     * <pre>
     * 1.首先根据任务id查询意见，应为意见在task创建时这个意见就被产生出来，所以能直接获取到该意见。
     * 2.获取意见，注意一个节点只允许一个意见填写，不能在同一个任务节点上同时允许两个意见框的填写，比如同时科员意见，局长意见等。
     * 3.更新意见。
     * </pre>
     *
     * @param options    意见
     * @param processCmd 流程执行命令实体
     */
    private void setOpinionByForm(Map<String, String> options, ProcessCmd processCmd) {
        if (BeanUtils.isEmpty(options)) {
            return;
        }
        Set<String> set = options.keySet();
        for (String key : set) {
            String value = options.get(key);
            processCmd.setVoteFieldName(key);
            if (StringUtil.isNotEmpty(value)) {
                processCmd.setVoteContent(value);
                break;
            }
        }
    }

    /**
     * 根据业务主键获取流程运行对象
     *
     * @param businessKey 业务主键
     * @return 流程运行对象
     */
    public ProcessRun getByBusinessKey(String businessKey) {
        if (StringUtil.isNotEmpty(businessKey)) {
            return processRunDao.getByBusinessKey(businessKey);
        } else {
            return null;
        }

    }

    /**
     * 根据业务主键获取流程运行对象列表
     *
     * @param businessKey 业务主键
     * @return 流程运行对象
     */
    public List<ProcessRun> getByBusinessKeyList(String businessKey) {
        if (StringUtil.isNotEmpty(businessKey)) {
            return processRunDao.getByBusinessKeyList(businessKey);
        } else {
            return null;
        }

    }

    /**
     * 判断特定业务主键是否已绑定流程实例
     *
     * @param businessKey 业务主键
     * @return boolean 返回结果
     */
    public boolean isProcessInstanceExisted(String businessKey) {
        if (StringUtil.isEmpty(businessKey)) {
            return false;
        }
        Integer rtn = processRunDao.getCountByBusinessKey(businessKey);
        return rtn > 0;
    }

    /**
     * 是否有处理任务的权限
     *
     * @param taskId 任务id
     * @param userId 用户id
     * @return 是否有权限
     */
    public boolean getHasRightsByTask(String taskId, String userId) {
        return taskDao.getHasRightsByTask(taskId, userId) > 0;
    }

    /**
     * 流程启动下一步
     *
     * @param processCmd 流程执行命令实体。
     * @throws Exception 异常
     */
    @Transactional(rollbackFor = Exception.class)
    public ProcessRun nextProcess(ProcessCmd processCmd) throws Exception {
        ProcessRun processRun;
        String taskId;
        // 获取任务对象
        TaskEntity taskEntity = getTaskEntByCmd(processCmd);
        if (taskEntity == null) {
            throw new Exception("task.not.exist");
        }
        String nodeId = taskEntity.getTaskDefinitionKey();
        taskId = taskEntity.getId();

        String taskStatus = taskEntity.getDescription();


        // 通知任务直接返回
        if (taskEntity.getExecutionId() == null && TaskOpinion.STATUS_COMMUNICATION.toString().equals(taskStatus)) {
            return null;
        }

        // 流转中任务，删除产生的流转状态和流转任务
        if (TaskOpinion.STATUS_TRANSTO_ING.toString().equals(taskStatus)) {
            this.handleInterveneTransTo(taskId);
        }

        // 当下一节点为条件同步节点时，可以指定执行路径
        Object nextPathObj = processCmd.getFormDataMap().get("nextPathId");
        if (nextPathObj != null) {
            bpmService.setTaskVariable(taskId, "NextPathId", nextPathObj.toString());
        }
        String procDefId = taskEntity.getProcessDefinitionId();
        String instanceId = taskEntity.getProcessInstanceId();
        ActDefModel actDefModel = actDefModelDao.getByProcDefId(procDefId);
        processRun = processRunDao.getByProcInstanceId(instanceId);
        processCmd.addTransientVar(BpmConst.BPM_DEF, actDefModel);
        processCmd.setProcessRun(processRun);

        // 取到当前任务是否带有分发令牌
        String taskToken = (String) taskService.getVariableLocal(taskId, TaskFork.TAKEN_VAR_NAME);
        String parentProcDefId = (String) taskService.getVariable(taskId, BpmConst.FLOW_PARENT_PROCDEFID);
        BpmNodeSet bpmNodeSet;
        if (StringUtil.isEmpty(parentProcDefId)) {
            bpmNodeSet = bpmNodeSetService.getByProcDefIdNodeId(procDefId, nodeId, null);
        } else {// 获取子流程节点数据
            bpmNodeSet = bpmNodeSetService.getByProcDefIdNodeId(procDefId, nodeId, parentProcDefId);
        }
        //将审批意见字段记录下来
        getOpinionField(processCmd, actDefModel.getProcDefId(), parentProcDefId);

        // 设置下一步包括分发任务的用户
        setThreadTaskUser(processCmd);
        // 处理在线表单的业务数据
        handFormData(processCmd, bpmNodeSet, processRun, nodeId);

        // 调用前置处理器
        if (!processCmd.isSkipPreHandler()) {
            invokeHandler(processCmd, bpmNodeSet, true);
        }

        // 3.流程回退处理的前置处理，查找该任务节点其回退的堆栈树父节点，以便其跳转时，能跳回该节点上
        ExecutionStack parentStack = backPrepare(processCmd, taskEntity, taskToken);

        String parentNodeId = nodeId;
        if (parentStack != null) {
            parentNodeId = parentStack.getNodeId();
        }
        // 设置会签用户或处理会签意见
        signUsersOrSignVoted(processCmd, taskEntity);
        // 设置流程变量。
        processCmd.setSubject(processRun.getSubject());
        setVariables(taskEntity, processCmd);

        // 若是自跳转方式
        if (BpmNodeSet.JUMP_TYPE_SELF.equals(processCmd.getJumpType())) {
            // 让他跳回本节点
            processCmd.setDestTask(taskEntity.getTaskDefinitionKey());
        }

        // 如果是干预添加干预审批数据
        addInterVene(processCmd, taskEntity);

        // 如果是追回的任务执行时，状态设为重新提交
        if (7 == processRun.getStatus()) {
            addRetrOrRecoverOpinion(processCmd, taskEntity, true);
        }

        // 完成当前任务
        completeTask(processCmd, taskEntity, bpmNodeSet.getIsJumpForDef());

        // 调用后置处理器这里
        if (!processCmd.isSkipAfterHandler()) {
            invokeHandler(processCmd, bpmNodeSet, false);
        }
        // 如果在流程运行主键为空，并且在processCmd不为空的情况下，我们更新流程运行的主键。
        if (StringUtil.isEmpty(processRun.getBusinessKey()) && StringUtil.isNotEmpty(processCmd.getBusinessKey())) {
            processRun.setBusinessKey(processCmd.getBusinessKey());
            // 设置流程主键变量。
            runtimeService.setVariable(instanceId, BpmConst.FLOW_BUSINESSKEY, processCmd.getBusinessKey());
        }
        // 任务回退时，弹出历史执行记录的堆栈树节点
        if (processCmd.isBack() > 0 && parentStack != null) {
            executionStackService.pop(parentStack);
        } else if (processCmd.isBack().equals(BpmConst.TASK_BACK_TODESTIDS)) {
            //自由回退，可能是多个节点
            String testDestIds = processCmd.getBackNodeIds();
            String[] destIdArray = testDestIds.split(",");
            for (int i = 0; i < destIdArray.length; i++) {
                // 获取发起节点。
                parentStack = executionStackService.getLastestStack(instanceId, destIdArray[i], null);
                if (parentStack != null) {
                    executionStackService.pop(parentStack);
                }
            }
        } else {
            // 记录执行执行的堆栈
            List<String> map = TaskThreadService.getExtSubProcess();
            if (BeanUtils.isEmpty(map)) {
                executionStackService.addStack(instanceId, parentNodeId, taskToken);
            } else {
                // 初始化外部子流程。
                ExecutionStack executionStack = executionStackService.getLastestStack(instanceId, taskEntity.getTaskDefinitionKey(), taskToken);
                initExtSubProcessStack(executionStack.getStackId());
            }
        }

        /*
         * 为了解决在任务自由跳转或回退时，若流程实例有多个相同Key的任务，会把相同的任务删除。 与TaskCompleteListner里的以下代码对应使用 if(processCmd!=null && (processCmd.isBack()>0 || StringUtils.isNotEmpty(processCmd.getDestTask()))){ taskDao.updateNewTaskDefKeyByInstIdNodeId (delegateTask.getTaskDefinitionKey() + "_1",delegateTask.getTaskDefinitionKey (),delegateTask.getProcessInstanceId()); }
         */
        if (processCmd.isBack() > 0 || StringUtils.isNotEmpty(processCmd.getDestTask())) {
            // 更新其相对对应的key
            Map<String, Object> params = new HashMap<>(3);
            String newTaskDefKey = taskEntity.getTaskDefinitionKey() + "_1";
            String oldTaskDefKey = taskEntity.getTaskDefinitionKey();
            String procInstId = taskEntity.getProcessInstanceId();
            params.put("newTaskDefKey", StringUtil.isNotEmpty(newTaskDefKey) ? (newTaskDefKey + "%") : newTaskDefKey);
            params.put("oldTaskDefKey", oldTaskDefKey);
            params.put("procInstId", procInstId);
            taskDao.updateOldTaskDefKeyByInstIdNodeId(params);
        }

        Map<String, String> map = new HashMap<>(1);
        if (processCmd.isBack() != 0) {
            map = sysTemplateService.getTempByFun(SysTemplate.USE_TYPE_REJECT);
        }
        // 处理信息
        taskMessageService.notify(TaskThreadService.getNewTasks(), "", processRun.getSubject(), map, processCmd.getVoteContent(), parentProcDefId);
        // 处理代理
        handleAgentTaskExe(processCmd);
        // 记录流程执行日志。
        recordLog(processCmd, taskEntity.getName(), processRun.getRunId());

        // 任务完成
        bpmTaskExeService.complete(taskId);

        List<Task> taskList = TaskThreadService.getNewTasks();
        // 有任务
        if (BeanUtils.isNotEmpty(taskList)) {
            //子流程实例结束时，产生主流程任务，会错误的更新子流程的状态
            if (taskList.get(0).getProcessDefinitionId().equals(processRun.getProcDefId())) {
                updateStatus(processCmd, processRun, taskList.get(0).getId());
            }
        }

        // 抛出nodeSql事件
        String action = getActionByCmdVoteAgree(processCmd.getVoteAgree());
        if (StringUtil.isNotEmpty(action)) {
            EventUtil.publishNodeSqlEvent(procDefId, nodeId, action, (Map) processCmd.getTransientVar("mainData"));

            EventUtil.publishTriggerNewFlowEvent(action, nodeId, processCmd);
        }

        //根据流程定义设置的节点跳过设定进行跳转
        handAutoJump(actDefModel, processCmd);
        //由于threadLocal总是没有清除，所以手动清除
        TaskThreadService.clearAll();
        return processRun;
    }

    /**
     * 初始化子流程任务堆栈。
     * <pre>
     * 子流程处理当作新的流程进行处理，进行初始化处理。
     * </pre>
     *
     * @param parentStackId 堆栈id
     */
    private void initExtSubProcessStack(String parentStackId) {
        List<String> extSubProcessList = TaskThreadService.getExtSubProcess();
        if (BeanUtils.isEmpty(extSubProcessList)) {
            return;
        }
        List<Task> taskList = TaskThreadService.getNewTasks();
        Map<String, List<Task>> map = getMapByTaskList(taskList);
        for (String instanceId : extSubProcessList) {
            List<Task> tmpList = map.get(instanceId);
            executionStackService.initStack(instanceId, tmpList, parentStackId);
        }
    }

    /**
     * 将任务列表转换成map
     *
     * @param taskList 任务列表
     * @return 任务集合
     */
    private Map<String, List<Task>> getMapByTaskList(List<Task> taskList) {
        Map<String, List<Task>> map = new HashMap<>(16);
        for (Task task : taskList) {
            String instanceId = task.getProcessInstanceId();
            if (map.containsKey(instanceId)) {
                map.get(instanceId).add(task);
            } else {
                List<Task> list = new ArrayList<>();
                list.add(task);
                map.put(instanceId, list);
            }
        }

        return map;
    }

    /**
     * 会签用户的设置及会签投票的处理。
     *
     * <pre>
     * 		1.从上下文中获取会签人员数据，如果会签人员数据不为空，则把人员绑定到线程，供会签任务节点产生会签用户使用。
     * 		2.如果从上下文中获取了投票的数据。
     * 		3.进行投票操作。
     * 		4.设置流程状态，设置会签的意见，状态。
     * </pre>
     *
     * @param processCmd 流程执行命令对象
     * @param taskEntity 任务
     */
    private void signUsersOrSignVoted(ProcessCmd processCmd, TaskEntity taskEntity) {
        // 处理后续的节点若有为多实例时，把页面中提交过来的多实例的人员放置至线程中，在后续的任务创建中进行获取
        String nodeId = taskEntity.getTaskDefinitionKey();
        String taskId = taskEntity.getId();
        // 判断当前任务是否会多实例会签任务
        boolean isSignTask = bpmService.isSignTask(taskEntity.getProcessDefinitionId(), nodeId);
        // 是会签任务将人员取出并设置会签人员。
        if (isSignTask) {
            // 添加补签意见
            // 查询是否存在审批意见，存在这说明是会签人员，不需要再添加审批意见，没有则说明是新添加的补签人员，需要添加审批意见
            TaskOpinion taskOpinion = taskOpinionService.getByTaskId(taskEntity.getId());

            if (BeanUtils.isEmpty(taskOpinion)) {
                addRetrOrRecoverOpinion(processCmd, taskEntity, false);
            }

            Map<String, List<TaskExecutor>> executorMap = processCmd.getTaskExecutor();
            if (executorMap != null && executorMap.containsKey(nodeId)) {
                List<TaskExecutor> executorList = executorMap.get(nodeId);
                taskUserAssignService.setExecutors(executorList);
            }
        }

        if (processCmd.getVoteAgree() != null) {
            // 加入任务的处理结果及意见
            if (isSignTask) {
                // 加上会签投票
                taskSignDataService.signVoteTask(taskId, processCmd.getVoteContent(), processCmd.getVoteAgree());
            }
            processCmd.getVariables().put(BpmConst.NODE_APPROVAL_STATUS + "_" + nodeId, processCmd.getVoteAgree());
        }

    }

    /**
     * 流程操作日志记录
     *
     * @param processCmd 流程执行命令对象
     */
    private void recordLog(ProcessCmd processCmd, String taskName, String runId) {
        String memo = "";
        Integer type = -1;
        // 撤销
        if (processCmd.isRecover()) {
            type = BpmRunLog.OPERATOR_TYPE_RETRIEVE;
            memo = "用户在任务节点[" + taskName + "]执行了撤销操作。";
        }
        // 驳回
        else if (BpmConst.TASK_BACK.equals(processCmd.isBack())) {
            type = BpmRunLog.OPERATOR_TYPE_REJECT;
            memo = "用户在任务节点[" + taskName + "]执行了驳回操作。";
        }
        // 驳回到发起人
        else if (BpmConst.TASK_BACK_TOSTART.equals(processCmd.isBack())) {
            type = BpmRunLog.OPERATOR_TYPE_REJECT2SPONSOR;
            memo = "用户在任务节点[" + taskName + "]执行了驳回到发起人操作。";
        } else {
            // 同意
            if (TaskOpinion.STATUS_AGREE.equals(processCmd.getVoteAgree())) {
                type = BpmRunLog.OPERATOR_TYPE_AGREE;
                memo = "用户在任务节点[" + taskName + "]执行了同意操作。";
            }
            // 反对
            else if (TaskOpinion.STATUS_REFUSE.equals(processCmd.getVoteAgree())) {
                type = BpmRunLog.OPERATOR_TYPE_OBJECTION;
                memo = "用户在任务节点[" + taskName + "]执行了反对操作。";
            }
            // 弃权
            else if (TaskOpinion.STATUS_ABANDON.equals(processCmd.getVoteAgree())) {
                type = BpmRunLog.OPERATOR_TYPE_ABSTENTION;
                memo = "用户在任务节点[" + taskName + "]执行了弃权操作。";
            }
            // 更改执行路径
            if (TaskOpinion.STATUS_CHANGEPATH.equals(processCmd.getVoteAgree())) {
                type = BpmRunLog.OPERATOR_TYPE_CHANGEPATH;
                memo = "用户在任务节点[" + taskName + "]执行了更改执行路径操作。";
            }
        }
        if (type == -1) {
            return;
        }
        bpmRunLogService.addRunLog(runId, type, memo);
    }

    /**
     * 回退准备。
     *
     * @param processCmd 流程执行命令对象
     * @param taskEntity 任务
     * @param taskToken  任务密钥
     * @return 堆栈信息
     * @throws Exception 异常
     */
    private ExecutionStack backPrepare(ProcessCmd processCmd, TaskEntity taskEntity, String taskToken) throws Exception {
        String instanceId = taskEntity.getProcessInstanceId();
        //正常跳转
        if (processCmd.isBack() == 0) {
            return null;
        }
        // 获取流程定义id
        String procDefId = taskEntity.getProcessDefinitionId();
        String backToNodeId = processCmd.getStartNode();

        //开始节点后的第一个用户任务节点
        if (StringUtils.isEmpty(backToNodeId)) {
            backToNodeId = Objects.requireNonNull(NodeCache.getFirstNodeId(procDefId)).getNodeId();
        }

        ExecutionStack parentStack = null;
        // 驳回
        if (processCmd.isBack().equals(BpmConst.TASK_BACK)) {
            parentStack = executionStackService.backPrepared(processCmd, taskEntity, taskToken);
        }
        // 驳回到发起人
        else if (processCmd.isBack().equals(BpmConst.TASK_BACK_TOSTART)) {
            // 获取发起节点。
            parentStack = executionStackService.getLastestStack(instanceId, backToNodeId, null);
            if (parentStack != null) {
                processCmd.setDestTask(parentStack.getNodeId());
                taskUserAssignService.addNodeUser(parentStack.getNodeId(), parentStack.getAssignees());
            }
        } else if (processCmd.isBack().equals(BpmConst.TASK_BACK_TODESTIDS)) {
            //自由回退，可能是多个节点,根据执行堆栈设置执行人
            String testDestIds = processCmd.getBackNodeIds();
            String[] destIdArray = testDestIds.split(",");
            for (int i = 0; i < destIdArray.length; i++) {
                // 获取发起节点。
                parentStack = executionStackService.getLastestStack(instanceId, destIdArray[i], null);
                if (parentStack != null) {
                    taskUserAssignService.addNodeUser(parentStack.getNodeId(), parentStack.getAssignees());
                }
            }
        }
        return parentStack;
    }

    /**
     * 执行节点跳过。
     *
     * @param processCmd  流程执行命令对象
     * @param actDefModel 流程模型
     * @throws Exception 异常
     */
    private void handAutoJump(ActDefModel actDefModel, ProcessCmd processCmd) throws Exception {
        List<Task> taskList = TaskThreadService.getNewTasks();
        if (BeanUtils.isEmpty(taskList) || processCmd.getIsManage() != 0) {
            return;
        }
        String skipSetting = actDefModel.getSkipSetting();
        if (StringUtil.isEmpty(skipSetting)) {
            return;
        }
        if (processCmd.isBack() != 0) {
            return;
        }

        taskUserAssignService.clearExecutors();

        while (!taskList.isEmpty()) {
            Task task = taskList.get(0);
            taskList.remove(0);
            boolean rtn = canSkip(skipSetting, task, processCmd);
            if (!rtn) {
                continue;
            }
            skipTask(task, processCmd);
        }

    }

    /**
     * 跳过节点。
     *
     * @param task       任务
     * @param processCmd 流程执行命令对象
     * @throws Exception 异常
     */
    private void skipTask(Task task, ProcessCmd processCmd) throws Exception {
        processCmd.setSkip(true);
        processCmd.setTaskId(task.getId());
        processCmd.setVoteAgree(TaskOpinion.STATUS_AGREE);
        processCmd.setDestTask("");
        nextProcess(processCmd);
        processCmd.setSkip(false);
    }

    /**
     * 判断任务是否能否跳过。
     *
     * @param skipSetting 跳过节点设置
     * @param task        任务
     * @return 是否能跳过
     */
    private boolean canSkip(String skipSetting, Task task, ProcessCmd processCmd) {
        Map<String, ISkipCondition> map = ServiceUtil.getSkipConditionMap();
        // 全局的直接跳过。
        if (skipSetting.contains(ActDefModel.SKIPSETTING_GLOBAL)) {
            ISkipCondition condition = map.get(ActDefModel.SKIPSETTING_GLOBAL);
            processCmd.addTransientVar("skipCondition", condition);
            return true;
        }

        String[] arySkip = skipSetting.split(",");
        for (String skip : arySkip) {
            ISkipCondition condition = map.get(skip);
            boolean rtn = condition.canSkip(task);
            if (rtn) {
                processCmd.addTransientVar("skipCondition", condition);
                return true;
            }
        }
        return false;
    }

    /**
     * 根据cmd的审批
     *
     * @param vote 审批意见
     * @return 审批意见字段
     */
    private String getActionByCmdVoteAgree(Short vote) {
        switch (vote) {
            case 1:
            case 5:
                return BpmNodeSql.ACTION_AGREE;
            case 2:
            case 6:
                return BpmNodeSql.ACTION_OPPOSITE;
            case 3:
                return BpmNodeSql.ACTION_REJECT;
            default:
                return null;
        }

    }

    /**
     * 根据命令对象获取任务
     *
     * @param processCmd 流程命令执行对象
     * @return 任务实体
     */
    private TaskEntity getTaskEntByCmd(ProcessCmd processCmd) {
        TaskEntity taskEntity = null;
        String taskId = processCmd.getTaskId();
        // 根据任务id获取任务
        if (StringUtil.isNotEmpty(taskId)) {
            taskEntity = bpmService.getTask(taskId);
        }
        return taskEntity;
    }

    /**
     * 级联删除流转任务（干预）
     *
     * @param taskId 任务id
     */
    private void handleInterveneTransTo(String taskId) {
        // 删除流转状态
        bpmProTransToService.delByTaskId(taskId);
        // 删除被流转任务产生的沟通任务
        taskDao.delCommuTaskByParentTaskId(taskId);
        bpmProTransToService.cancelTransToTaskCascade(taskId);
    }

    /**
     * 获取意见属性
     *
     * @param processCmd      流程执行命令实体
     * @param procDefId       流程定义id
     * @param parentProcDefId 父流程定义id
     */
    private void getOpinionField(ProcessCmd processCmd, String procDefId, String parentProcDefId) {
        List<BpmNodeSet> bpmNodeSets;
        List<String> list = new ArrayList<>();
        if (StringUtil.isEmpty(parentProcDefId)) {
            bpmNodeSets = bpmNodeSetService.queryByDefIdOpinion(procDefId);
        } else {
            bpmNodeSets = bpmNodeSetService.queryByParentDefIdOpinion(procDefId, parentProcDefId);
        }
        for (BpmNodeSet bpmNodeSet : bpmNodeSets) {
            list.add(bpmNodeSet.getOpinionField());
        }
        processCmd.addTransientVar("opinionFields", list);
    }

    /**
     * 设置流程变量。
     *
     * @param entity     任务
     * @param processCmd 流程执行命令实体
     */
    private void setVariables(TaskEntity entity, ProcessCmd processCmd) {
        if (BeanUtils.isEmpty(entity)) {
            return;
        }

        processCmd.getVariables().put("isBack", processCmd.isBack());
        String taskId = entity.getId();
        Map<String, Object> vars = processCmd.getVariables();
        if (BeanUtils.isNotEmpty(vars)) {
            for (Map.Entry<String, Object> obj : vars.entrySet()) {
                runtimeService.setVariable(entity.getProcessInstanceId(), obj.getKey(), obj.getValue());
            }
        }
        Map<String, Object> formVars = taskService.getVariables(taskId);
        formVars.put(BpmConst.IS_EXTERNAL_CALL, processCmd.isInvokeExternal());
        formVars.put(BpmConst.FLOW_RUN_SUBJECT, processCmd.getSubject());
        // 设置线程变量。
        TaskThreadService.setVariables(formVars);
        TaskThreadService.putVariables(processCmd.getVariables());
    }

    /**
     * 添加干预数据。
     *
     * <pre>
     * 	添加干预数据到到审批历史。
     * </pre>
     *
     * @param processCmd 流程执行命令实体
     * @param taskEnt    任务
     */
    private void addInterVene(ProcessCmd processCmd, DelegateTask taskEnt) {
        if (processCmd.getIsManage() == 0) {
            return;
        }
        String assignee = taskEnt.getAssignee();
        StringBuilder tmp = new StringBuilder();

        SysUser curUser = (SysUser) ContextUtil.getCurrentUser();
        String userId = SystemConst.SYSTEMUSERID;
        String userName = SystemConst.SYSTEMUSERNAME;
        if (curUser != null) {
            userId = curUser.getUserId();
            userName = curUser.getFullName();
        }

        if (ServiceUtil.isAssigneeNotEmpty(assignee)) {
            SysUser sysUser = sysUserDao.getById(assignee);
            if (sysUser != null) {
                tmp = new StringBuilder("原执行人:" + ServiceUtil.getUserLink(sysUser.getUserId(), sysUser.getFullName()));
            }
        } else {
            tmp = new StringBuilder("原候选执行人:");
            Set<SysUser> userList = taskUserService.getCandidateUsers(taskEnt.getId());
            for (SysUser user : userList) {
                if (user != null) {
                    tmp.append(ServiceUtil.getUserLink(user.getUserId(), user.getFullName())).append(",");
                }
            }
        }
        Date endDate = new Date();
        TaskOpinion taskOpinion = new TaskOpinion();
        taskOpinion.setOpinionId(UUIDUtils.getUUIDFor32());
        taskOpinion.setProcDefId(taskEnt.getProcessDefinitionId());
        taskOpinion.setProcInstId(taskEnt.getProcessInstanceId());
        taskOpinion.setTaskKey(taskEnt.getTaskDefinitionKey());
        taskOpinion.setTaskName(taskEnt.getName());
        taskOpinion.setExeUserId(userId);
        taskOpinion.setExeFullname(userName);
        taskOpinion.setCheckStatus(TaskOpinion.STATUS_INTERVENE);
        taskOpinion.setStartTime(taskEnt.getCreateTime());
        taskOpinion.setTaskId(taskEnt.getId());
        taskOpinion.setCreateTime(taskEnt.getCreateTime());
        taskOpinion.setEndTime(endDate);
        taskOpinion.setOpinion(tmp.toString());
        Long duration = endDate.getTime() - taskEnt.getCreateTime().getTime();
        taskOpinion.setDurTime(duration);
        taskOpinionService.add(taskOpinion);

    }

    /**
     * 补签或追回后重新提交时添加审批意见。
     *
     * @param processCmd 流程执行命令实体
     * @param taskEntity 任务
     * @param isRecover  是否追回
     */
    private void addRetrOrRecoverOpinion(ProcessCmd processCmd, TaskEntity taskEntity, boolean isRecover) {
        String opinion = processCmd.getVoteContent();
        String attachment = processCmd.getAttachment();
        String opinionId = UUIDUtils.getUUIDFor32();
        SysUser sysUser = (SysUser) ContextUtil.getCurrentUser();

        TaskOpinion taskOpinion = new TaskOpinion();
        taskOpinion.setOpinionId(opinionId);
        taskOpinion.setOpinion(opinion);
        taskOpinion.setAttachment(attachment);
        taskOpinion.setTaskId(taskEntity.getId());
        taskOpinion.setProcDefId(taskEntity.getProcessDefinitionId());
        taskOpinion.setProcInstId(taskEntity.getProcessInstanceId());
        //20191129 jyq修改，将开始时间改为任务创建的时间
        Date nowDate = new Date();
        Date createTime = taskEntity.getCreateTime() == null ? nowDate : taskEntity.getCreateTime();
        taskOpinion.setStartTime(createTime);
        taskOpinion.setEndTime(nowDate);
        taskOpinion.setDurTime(nowDate.getTime() - createTime.getTime());
        taskOpinion.setExeUserId(sysUser.getUserId());
        taskOpinion.setExeFullname(sysUser.getFullName());
        taskOpinion.setTaskKey(taskEntity.getTaskDefinitionKey());
        taskOpinion.setTaskName(taskEntity.getName());
        if (isRecover) {
            taskOpinion.setCheckStatus(TaskOpinion.STATUS_RESUBMIT);
        } else {
            taskOpinion.setCheckStatus(TaskOpinion.STATUS_AGREE);
        }

        // 处理审批意见的父流程ID
        this.dealTaskOpinSupId(taskEntity, taskOpinion);

        // 增加流程意见
        taskOpinionDao.add(taskOpinion);

    }


    /**
     * 完成任务跳转。
     *
     * @param processCmd   ProcessCmd对象
     * @param taskEntity   流程任务实例
     * @param isJumpForDef 规则不作用时，是否正常跳转(1,正常跳转,0,不跳转)
     * @throws Exception 异常
     */
    private void completeTask(ProcessCmd processCmd, TaskEntity taskEntity, Short isJumpForDef) throws Exception {
        SysUser sysUser = (SysUser) ContextUtil.getCurrentUser();
        String procInstId = taskEntity.getProcessInstanceId();
        String procDefId = taskEntity.getProcessDefinitionId();
        CommandExecutor commandExecutor = FlowableUtil.getCommandExecutor();
        // 子流程的驳回
        boolean rtn = bpmFlowableService.allowCallActivityBack(taskEntity.getId());
        if (rtn) {
            bpmFlowableService.reject(taskEntity.getId(), processCmd.getDestTask());
            return;
        }
        String taskId = taskEntity.getId();
        if (processCmd.isOnlyCompleteTask()) {
            bpmService.onlyCompleteTask(taskId);
        } else if (processCmd.isBack().equals(4)) {
            //2020-01-19 jyq添加 驳回到任意路径
            String nodeId = taskEntity.getTaskDefinitionKey();
            String testDestIds = processCmd.getBackNodeIds();
            String[] destIdArray = testDestIds.split(",");
            List<String> destIds = new ArrayList<>();
            for (int i = 0; i < destIdArray.length; i++) {
                destIds.add(destIdArray[i]);
            }
            runtimeService.createChangeActivityStateBuilder().processInstanceId(procInstId)
                    .moveSingleActivityIdToActivityIds(nodeId, destIds).changeState();
            //驳回之后，将驳回节点之后的任务删除掉
            //查询所有正在处理的任务，目前只考虑主流程
            List<Task> taskList = bpmService.queryTasksByProcInstId(procInstId);
            if (taskList.size() > 0) {
                //遍历任务
                Iterator<Task> taskIterator = taskList.iterator();
                while (taskIterator.hasNext()) {
                    TaskEntityImpl taskEntity1 = (TaskEntityImpl) taskIterator.next();
                    for (int i = 0; i < destIdArray.length; i++) {
                        if (taskEntity1.getTaskDefinitionKey().equals(destIdArray[i])) {
                            continue;
                        }
                        CheckReachableCmd checkReachableCmd = new CheckReachableCmd(procDefId, destIdArray[i], taskEntity1.getTaskDefinitionKey());
                        Boolean isReachable = commandExecutor.execute(checkReachableCmd);
                        if (isReachable) {
                            //删除该任务以及到达该任务的执行流，由于flowable内部api存在限制因此不使用内部api
                            taskDao.delRuTaskByTaskId(taskEntity1.getId());
                            taskDao.delActRuIdentitylink(taskEntity1.getId());
                            //删除节点执行状态
                            //bpmProStatusDao.delByProcInstIdAndNodeId(procInstId,taskEntity1.getTaskDefinitionKey());
                            //删除任务审批意见
                            taskOpinionDao.delByTaskId(taskEntity1.getId());
                            ExecutionEntity executionEntity = (ExecutionEntity) runtimeService.createExecutionQuery().processInstanceId(procInstId).activityId(taskEntity1.getTaskDefinitionKey()).singleResult();
                            actDefModelDao.delActRuExecutionById(executionEntity.getId());
                            break;
                        }
                    }
                }
            }
            //根据目标节点去查找流程执行堆栈，在BPM_TASK_OPINION表中新增一条驳回的意见，修改BPM_PRO_STATUS数据
            for (int i = 0; i < destIdArray.length; i++) {
                //删除当前堆栈。
                ExecutionStack parentStack = executionStackService.getLastestStack(procInstId, destIdArray[i], null);
                if (null != parentStack) {
                    List<ExecutionStack> list = executionStackService.findChildsByParentId(parentStack);
                    for (ExecutionStack stack : list) {
                        //更新节点状态为驳回
                        bpmProStatusService.updStatus(procInstId, stack.getNodeId(), TaskOpinion.STATUS_REJECT);
                        if (!stack.getNodeId().equals(taskEntity.getTaskDefinitionKey())) {
                            //新增审批数据为驳回
                            String opinionId = UUIDUtils.getUUIDFor32();
                            TaskOpinion taskOpinion = new TaskOpinion();
                            taskOpinion.setOpinionId(opinionId);
                            taskOpinion.setOpinion("系统驳回");
                            taskOpinion.setProcDefId(procDefId);
                            taskOpinion.setProcInstId(procInstId);
                            taskOpinion.setStartTime(new Date());
                            taskOpinion.setEndTime(new Date());
                            taskOpinion.setDurTime(0L);
                            taskOpinion.setExeUserId(sysUser.getUserId());
                            taskOpinion.setExeFullname(sysUser.getFullName());
                            taskOpinion.setTaskKey(stack.getNodeId());
                            taskOpinion.setTaskName(stack.getNodeName());
                            taskOpinion.setCheckStatus(TaskOpinion.STATUS_REJECT);
                            taskOpinionService.add(taskOpinion);
                        }
                    }
                }
            }

        } else if (StringUtils.isNotEmpty(processCmd.getDestTask())) {
            // 自由跳转或回退
            bpmService.transTo(taskEntity, processCmd.getDestTask());
        } else {
            // 正常流程跳转
            // 从规则中获取跳转
            String jumpTo = jumpRule.evaluate(taskEntity, isJumpForDef);
            bpmService.transTo(taskEntity, jumpTo);
        }

        //2020-01-19 jyq添加，主要是针对58所不标准的带条件分支的流程，在每次任务完成之后都检查一遍所有的执行流，
        //若某个执行流执行到某个条件分支，并且具备结束的条件，则让其继续执行下一节点
        CheckInclusiveGatewayCmd cmd = new CheckInclusiveGatewayCmd(procInstId);
        commandExecutor.execute(cmd);
    }

    /**
     * 处理转办或代理
     *
     * @param cmd 流程命令执行对象
     */
    private void handleAgentTaskExe(ProcessCmd cmd) throws Exception {
        List<Task> taskList = TaskThreadService.getNewTasks();
        if (BeanUtils.isEmpty(taskList)) {
            return;
        }
        for (Task taskEntity : taskList) {
            if (!TaskOpinion.STATUS_AGENT.toString().equals(taskEntity.getDescription())) {
                continue;
            }
            String procDefId = taskEntity.getProcessDefinitionId();
            String nodeId = taskEntity.getTaskDefinitionKey();
            ActDefModel actDefModel = actDefModelDao.getByProcDefId(procDefId);
            BpmNodeSet bpmNodeSet = bpmNodeSetService.getByProcDefIdNodeId(procDefId, nodeId, null);
            String informType = cmd.getInformType();
            if (StringUtil.isEmpty(informType)) {
                if (ActDefModel.STATUS_TEST.equals(actDefModel.getStatus()) && StringUtil.isNotEmpty(bpmNodeSet.getInformType())) {
                    informType = bpmNodeSet.getInformType();
                } else {
                    informType = actDefModel.getInformType();
                }
                // 设置代理消息的发送类型（内部消息） cmd.setInformType("3");
                cmd.setInformType(informType);
            }

            String assigneeId = taskEntity.getAssignee();
            SysUser auth = sysUserDao.getById(taskEntity.getOwner());
            SysUser agent = sysUserDao.getById(assigneeId);
            addAgentTaskExe(taskEntity, cmd, auth, agent);
        }
    }

    /**
     * 更新状态
     *
     * @param cmd        流程执行命令实体
     * @param processRun 流程运行对象
     *                   2019-11-21 新增逻辑：修改任务下一节点task的description（任务描述）
     */
    private void updateStatus(ProcessCmd cmd, ProcessRun processRun, String newTaskId) {
        boolean isRecover = cmd.isRecover();
        int isBack = cmd.isBack();
        switch (isBack) {
            case 0:
                // 正常
                processRun.setStatus(ProcessRun.STATUS_RUNNING);
                break;
            case 1:
                if (isRecover) {
                    //撤销
                    processRun.setStatus(ProcessRun.STATUS_RECOVER);
                    taskDao.updateTaskDescription(TaskOpinion.STATUS_RECOVER.toString(), newTaskId);
                } else {
                    //驳回
                    processRun.setStatus(ProcessRun.STATUS_REJECT);
                    taskDao.updateTaskDescription(TaskOpinion.STATUS_REJECT.toString(), newTaskId);
                }
                break;
            case 2:
                if (isRecover) {
                    //撤销到发起人
                    processRun.setStatus(ProcessRun.STATUS_RECOVER);
                    taskDao.updateTaskDescription(TaskOpinion.STATUS_RECOVER_TOSTART.toString(), newTaskId);
                } else {
                    // 驳回到发起人
                    processRun.setStatus(ProcessRun.STATUS_REJECT);
                    taskDao.updateTaskDescription(TaskOpinion.STATUS_REJECT_TOSTART.toString(), newTaskId);
                }
                break;
            default:
        }
        this.update(processRun);

    }

    /**
     * 添加代理数据。
     *
     * @param task  任务实例
     * @param cmd   ProcessCmd对象
     * @param auth  授权人
     * @param agent 代理人
     */
    private void addAgentTaskExe(Task task, ProcessCmd cmd, SysUser auth, SysUser agent) throws Exception {

        ProcessRun processRun = cmd.getProcessRun();
        if (processRun == null) {
            processRun = this.getByProcInstanceId(task.getProcessInstanceId());
        }
        String informType = cmd.getInformType();

        String memo = "[" + auth.getFullName() + "]自动代理给[" + agent.getFullName() + "]";
        String processSubject = processRun.getSubject();

        //ContextUtil.
        SysOrg sysorg = sysOrgDao.getOrgByUserId(auth.getUserId());

        String tenid = sysOrgDao.getTenIdByOrgId(sysorg.getOrgId());

        BpmTaskExe bpmTaskExe = new BpmTaskExe();
        bpmTaskExe.setId(UUIDUtils.getUUIDFor32());
        bpmTaskExe.setTaskId(task.getId());
        bpmTaskExe.setAssigneeId(agent.getUserId());
        bpmTaskExe.setAssigneeName(agent.getFullName());
        bpmTaskExe.setOwnerId(auth.getUserId());
        bpmTaskExe.setOwnerName(auth.getFullName());
        bpmTaskExe.setSubject(processSubject);
        bpmTaskExe.setStatus(BpmTaskExe.STATUS_INIT);
        bpmTaskExe.setMemo(memo);
        bpmTaskExe.setCreateTime(new Date());
        bpmTaskExe.setProcInstId(task.getProcessInstanceId());
        bpmTaskExe.setTaskDefKey(task.getTaskDefinitionKey());
        bpmTaskExe.setTaskName(task.getName());
        bpmTaskExe.setAssignType(BpmTaskExe.TYPE_ASSIGNEE);
        bpmTaskExe.setRunId(processRun.getRunId());
        bpmTaskExe.setTypeId(processRun.getTypeId());
        bpmTaskExe.setInformType(informType);
        bpmTaskExe.setTenantId(tenid + "");
        bpmTaskExeService.assignSave(bpmTaskExe);
    }

    /**
     * 通过流程实例id获取流程运行对象
     *
     * @param procInstId 流程实例id
     * @return 流程运行对象
     */
    public ProcessRun getByProcInstanceId(String procInstId) {
        ProcessRun processRun = processRunDao.getByProcInstanceId(procInstId);
        if (processRun == null) {
            return processRunDao.getByActInstanceIdHistory(procInstId);
        } else {
            return processRun;
        }
    }

    /**
     * 根据流程历史实例获取流程运行对象
     *
     * @param procInstId 流程实例id
     * @return 流程运行对象
     */
    public ProcessRun getByProcInstanceIdFromHis(String procInstId) {
        return processRunDao.getByProcInstanceIdFromHis(procInstId);
    }

    /**
     * 处理流程时表单数据跟流程数据
     *
     * @param processCmd 流程执行对象
     * @param formData   表单数据
     */
    private void handleData(ProcessCmd processCmd, FormData formData) {
        Map formDataMap = processCmd.getFormDataMap();
        Map data = new HashMap<>(16);
        data.putAll(formDataMap);
        data.putAll(formData.getMainFields());
        data.remove("formData");
        processCmd.addTransientVar("bpmFormData", formData);
        processCmd.addTransientVar("mainData", data);
    }

    /**
     * 将意见字段映射到字段中。
     *
     * @param processCmd 流程执行命令对象
     * @param formData   表单id
     */
    private static void handFieldOpinion(ProcessCmd processCmd, FormData formData) {
        String opinionField = (String) processCmd.getTransientVar(BpmConst.OPINION_FIELD);
        Short optionHtml = (Short) processCmd.getTransientVar(BpmConst.OPINION_SUPPORTHTML);
        List<String> opinionFields = (List<String>) processCmd.getTransientVar("opinionFields");
        if (StringUtil.isEmpty(opinionField)) {
            return;
        }
        opinionField = opinionField.toLowerCase();

        String opinion = processCmd.getVoteContent();
        int vote = processCmd.getVoteAgree();

        Map<String, Object> mainMap = formData.getMainFields();
        if (BeanUtils.isNotEmpty(opinionFields)) {
            for (String field : opinionFields) {
                field = field.toLowerCase();
                if (!mainMap.containsKey(field)) {
                    continue;
                }
                Object v = mainMap.get(field);
                if (v == null) {
                    mainMap.remove(field);
                } else if (v instanceof String) {
                    if (StringUtil.isEmpty(v.toString())) {
                        mainMap.remove(field);
                    }
                }
            }
        }

        SysUser sysUser = (SysUser) ContextUtil.getCurrentUser();
        String userName = (sysUser == null) ? "系统用户" : sysUser.getFullName();
        TaskOpinion taskOpinion = new TaskOpinion();
        taskOpinion.setExeFullname(userName);
        taskOpinion.setCheckStatus((short) vote);
        taskOpinion.setCreateTime(new Date());
        taskOpinion.setOpinion(opinion);
        if (processCmd.isSkip()) {
            taskOpinion.setOpinion("同意(自动审批)");
        }

        boolean supportHtml = optionHtml == 1;
        String val = TaskOpinionService.getOpinion(taskOpinion, supportHtml);
        //批量审批标记
        boolean batComplte = processCmd.getTransientVar("batComplte") != null ? (Boolean) processCmd.getTransientVar("batComplte") : false;
        if (batComplte) {
            val = "(批量审批)" + val;
        }
        if (mainMap.containsKey(opinionField)) {
            String str = (String) mainMap.get(opinionField);
            if (!StringUtil.isEmpty(str)) {
                val = str + "\n" + val;
            }
        }
        mainMap.put(opinionField, val);

    }

    /**
     * 处理动态表单数据
     *
     * @param formData   表单数据
     * @param processRun 流程运行对象
     * @param nodeId     节点id
     * @throws Exception 异常
     */
    public void handFormData(FormData formData, ProcessRun processRun, String nodeId) throws Exception {
        JdbcTemplate jt;
        FormTable formTable = formData.getFormTable();
        //加密主表数据
        dataTemplateService.getEncryptMainFields(formData.getTableId(), formData);
        //加密子表数据
        dataTemplateService.getEncryptSubFields(formData.getTableId(), formData);

        String procDefId = "";
        if (processRun != null) {
            procDefId = processRun.getProcDefId();
        }
        List<SqlModel> list = new ArrayList<>();
        if (formTable != null) {
            jt = ServiceUtil.getJdbcTemplate(formTable.getDsAlias());
            list = FormDataUtil.parseSql(formData, procDefId, nodeId);
            for (SqlModel sqlModel : list) {
                String sql = sqlModel.getSql();
                if (StringUtil.isEmpty(sql)) {
                    continue;
                }
                Object[] obs = sqlModel.getValues();
                int result = jt.update(sql, obs);
                if (result == 0 && FormTable.NEED_VER.equals(formTable.getNeedVer())) {
                    throw new RuntimeException("表单已被更新，请刷新后重试");
                }
            }
        }
        //处理关联数据。
        handlerBusLinkData(list, processRun);
        //20200513 主表增加密级字段dataPrivacyLevel 新增和修改时候需要同步sys_file密级字段的值
        handlerFilePrivacyData(list);
    }

    /**
     * 20200513 主表增加密级字段dataPrivacyLevel 新增和修改时候需要同步sys_file密级字段的值
     *
     * @param list sql列表
     */
    private void handlerFilePrivacyData(List<SqlModel> list) {
        for (SqlModel sqlModel : list) {
            String sql = sqlModel.getSql();
            if (StringUtil.isEmpty(sql)) {
                continue;
            }
            Object[] objArr = sqlModel.getValues();
            for (Object item : objArr) {
                if(null != item){
                    String str = item.toString();
                    if (StringUtil.isJson(str) && str.contains(FormConstants.FILE_ID) && str.contains(FormConstants.FILE_LEVEL)) {
                        JSONArray jsonArray = JSONArray.parseArray(str);
                        for(int i=0;i<jsonArray.size();i++) {
                            JSONObject jsonObject = jsonArray.getJSONObject(i);
                            String fileId = jsonObject.getString(FormConstants.FILE_ID);
                            int fileLevel = jsonObject.getInteger(FormConstants.FILE_LEVEL);
                            SysFile file = sysFileService.getOneById(fileId);
                            if(BeanUtils.isNotEmpty(file)){
                                file.setFileLevel(fileLevel);
                                sysFileService.update(file);
                            }
                        }
                    }
                }
            }
        }
    }

    /**
     * 处理关联数据。
     *
     * @param list       sql列表
     * @param processRun 流程运行对象
     */
    private void handlerBusLinkData(List<SqlModel> list, ProcessRun processRun) {
        for (SqlModel sqlModel : list) {
            String sql = sqlModel.getSql();
            if (StringUtil.isEmpty(sql)) {
                continue;
            }
            switch (sqlModel.getSqlType()) {
                case SqlModel.SQLTYPE_INSERT:
                    bpmBusLinkService.addBusLink(sqlModel.getPk(), processRun, sqlModel.getFormTable());
                    break;
                case SqlModel.SQLTYPE_UPDATE:
                    bpmBusLinkService.updateBusLink(sqlModel.getPk(), processRun, sqlModel.getFormTable());
                    break;
                case SqlModel.SQLTYPE_DEL:
                    bpmBusLinkService.delBusLink(sqlModel.getPk(), sqlModel.getFormTable());
                    break;
                default:
                    break;
            }
        }
    }

    /**
     * 办结事宜列表
     *
     * @param queryFilter 过滤条件
     * @return 流程运行对象
     */
    public List<ProcessRun> queryCompletedMattersList(QueryFilter queryFilter) {
        return processRunDao.queryCompletedMattersList(queryFilter);
    }

    /**
     * 我的请求和我的办结流程分类列表
     *
     * @param userId 用户id
     * @return 分类列表
     */
    public List<GlobalType> queryMyRequestAndMyCompletedTypeList(String userId, String category) {
        List<GlobalType> list = processRunDao.queryMyRequestAndMyCompletedTypeList(userId, category);
        SysTypeKey sysTypeKey = sysTypeKeyDao.getSysTypeKeyByKey(GlobalType.CAT_FLOW);
        GlobalType globalType = new GlobalType();
        globalType.setTypeName(sysTypeKey.getTypeName());
        globalType.setParentId("0");
        globalType.setIsParent("true");
        globalType.setTypeId(sysTypeKey.getTypeId());
        globalType.setType(sysTypeKey.getType());
        globalType.setNodePath(sysTypeKey.getTypeId() + ".");
        list.add(0, globalType);
        return list;
    }

    /**
     * 我的请求列表
     *
     * @param queryFilter 过滤条件
     * @return 请求列表
     */
    public List<ProcessRun> queryMyRequestList(QueryFilter queryFilter) {
        return processRunDao.queryMyRequestList(queryFilter);
    }

    /**
     * 我的办结列表
     *
     * @param queryFilter 过滤条件
     * @return 办结事宜列表
     */
    public List<ProcessRun> queryMyCompletedList(QueryFilter queryFilter) {
        return processRunDao.queryMyCompletedList(queryFilter);
    }

    /**
     * 草稿列表
     *
     * @param queryFilter 过滤条件
     * @return 草稿列表
     */
    public List<ProcessRun> queryMyDraft(QueryFilter queryFilter) {
        return processRunDao.queryMyDraft(queryFilter);
    }

    /**
     * 根据流程运行Id,删除流程运行实体。<br/>
     * 些方法不会级联删除相关信息。
     */
    @Transactional(rollbackFor = Exception.class)
    public void delById(String id) {
        processRunDao.delById(id);
        processRunDao.delActHiProcessInstanceByRunId(id);
        processRunDao.delByIdHistory(id);
    }

    /**
     * 复制草稿
     *
     * @param runId 流程运行id
     * @throws Exception 异常
     */
    @Transactional(rollbackFor = Exception.class)
    public void copyDraft(String runId) throws Exception {
        ProcessRun processRun = processRunDao.getById(runId);
        String newRunId = UUIDUtils.getUUIDFor32();
        processRun.setRunId(newRunId);
        String businessKey = processRun.getBusinessKey();
        String newBusKey = UUIDUtils.getUUIDFor32();
        FormTable bpmFormTable = formTableService.getByAliasTableName(processRun.getDsAlias(), processRun.getTableName());
        if (BeanUtils.isNotEmpty(bpmFormTable)) {
            // 查找源数据(未经处理的源数据 便于后面的插入数据)
            FormData bpmFormData = formHandlerService.getByKey(bpmFormTable.getTableId(), businessKey, null, null, false);
            // 设置新的主键
            PkValue pkValue = new PkValue();
            pkValue.setIsAdd(true);
            pkValue.setValue(newBusKey);
            pkValue.setName(bpmFormData.getPkValue().getName());
            bpmFormData.setPkValue(pkValue);

            Map<String, Object> mainData = bpmFormData.getMainFields();
            CaseInsensitiveMap caseInsensitiveMap = new CaseInsensitiveMap(mainData);
            caseInsensitiveMap.put(pkValue.getName(), newBusKey);
            bpmFormData.setMainFields(caseInsensitiveMap);
            // 子表数据绑定 主表新主键
            List<SubTable> subTableList = bpmFormData.getSubTableList();
            List<SubTable> newSubTables = new ArrayList<>();
            for (SubTable subTable : subTableList) {
                List<Map<String, Object>> subDatas = subTable.getDataList();
                List<Map<String, Object>> newSubDatas = new ArrayList<>();
                for (Map<String, Object> subData : subDatas) {
                    subData.put(subTable.getFkName(), newBusKey);
                    subData.put(subTable.getPkName(), UUIDUtils.getUUIDFor32());
                    newSubDatas.add(subData);
                }
                subTable.setDataList(newSubDatas);
                newSubTables.add(subTable);
            }
            bpmFormData.setSubTableList(newSubTables);
            this.handFormData(bpmFormData, processRun, newRunId);
            processRun.setBusinessKey(newBusKey);
            processRun.setCreateTime(new Date());
            add(processRun);
        }
    }

    /**
     * 批量删除
     *
     * @param ids 删除对象id
     * @throws Exception 异常
     */
    @Transactional(rollbackFor = Exception.class)
    public void delByIds(String[] ids) throws Exception {
        if (ids == null || ids.length == 0) {
            return;
        }
        for (String uId : ids) {
            ProcessRun processRun = getById(uId);
            deleteByProcessRun(processRun);
        }
    }

    /**
     * 刪除流程實例
     *
     * @param processRun 流程运行对象
     * @throws Exception 异常
     */
    @Transactional(rollbackFor = Exception.class)
    public void deleteByProcessRun(ProcessRun processRun) throws Exception {
        String uId = processRun.getRunId();
        Short procStatus = processRun.getStatus();
        String instanceId = processRun.getProcInstId();
        String dsAlias = processRun.getDsAlias();
        String tableName = processRun.getTableName();
        String businessKey = processRun.getBusinessKey();

        // 删除流程流转状态
        bpmProTransToService.delByProcInstId(instanceId);
        // 删除BPM_PRO_CPTO抄送转发
        processCopyToDao.delByRunId(uId);
        // 删除流程执行堆栈
        executionStackDao.delByProcInstId(instanceId);
        // 删除任务审批意见
        taskOpinionDao.delByProcInstId(instanceId);
        // 删除节点审批状态
        bpmProStatusDao.delByProcInstId(instanceId);
        // 删除任务查看信息
        taskReadDao.delByProcInstId(instanceId);
        // 删除流程表单运行记录
        bpmFormRunDao.delByInstanceId(instanceId);
        // 删除转办记录
        bpmTaskExeService.delByRunId(uId);
        if (!ProcessRun.STATUS_FINISH.equals(procStatus) && !ProcessRun.STATUS_FORM.equals(procStatus)) {
            deleteProcessInstance(processRun);
        } else {
            // 流程操作日志
            String memo = "用户删除了流程实例[" + processRun.getProcessName() + "]。";
            if (ProcessRun.STATUS_FORM.equals(procStatus)) {
                memo = "用户删除了流程草稿【" + processRun.getProcessName() + "】。";
                bpmRunLogService.addRunLog(processRun.getRunId(), BpmRunLog.OPERATOR_TYPE_DELETEFORM, memo);
            } else {
                bpmRunLogService.addRunLog(processRun.getRunId(), BpmRunLog.OPERATOR_TYPE_DELETEINSTANCE, memo);
            }
            delById(uId);
        }
        if (StringUtil.isNotEmpty(tableName)) {
            if (StringUtil.isEmpty(dsAlias) || DataSourceUtil.DEFAULT_DATASOURCE.equals(dsAlias)) {
                if (!tableName.startsWith(TableModel.CUSTOMER_TABLE_PREFIX)) {
                    tableName = TableModel.CUSTOMER_TABLE_PREFIX + tableName;
                }
                formHandlerService.delByIdTableName(businessKey, tableName);
            } else {
                try {
                    formHandlerService.delByDsAliasAndTableName(dsAlias, tableName, businessKey);
                } catch (Exception e) {
                    e.printStackTrace();
                    throw e;
                }
            }
        }
    }

    /**
     * 删除流程运行实体（ProcessRun），级联删除Act流程实例以及父流程
     *
     * @param processRun 流程运行对象
     */
    private void deleteProcessInstance(ProcessRun processRun) {
        ProcessRun rootProcessRun = getRootProcessRun(processRun);
        deleteProcessRunCasade(rootProcessRun);
    }

    /**
     * 级联删除流程实例扩展
     *
     * @param processRun 流程运行对象
     */
    private void deleteProcessRunCasade(ProcessRun processRun) {
        List<ProcessInstance> childrenProcessInstance = runtimeService.createProcessInstanceQuery().superProcessInstanceId(processRun.getProcInstId()).list();
        for (ProcessInstance instance : childrenProcessInstance) {
            ProcessRun pr = getByProcInstanceId(instance.getProcessInstanceId());
            if (pr != null) {
                deleteProcessRunCasade(pr);
            }
        }
        String procInstId = processRun.getProcInstId();
        Short procStatus = processRun.getStatus();
        if (!ProcessRun.STATUS_FINISH.equals(procStatus)) {
            executionDao.delVariableByProcInstId(procInstId);
            taskDao.delCandidateByInstanceId(procInstId);
            taskDao.delByInstanceId(procInstId);
            executionDao.delExecutionByProcInstId(procInstId);
        }
        String procDefId = processRun.getProcDefId();
        ActDefModel actDefModel = actDefModelDao.getByProcDefId(procDefId);
        if (!ActDefModel.STATUS_TEST.equals(actDefModel.getStatus())) {
            String memo = "用户删除了流程实例[" + processRun.getProcessName() + "]。";
            bpmRunLogService.addRunLog(processRun, BpmRunLog.OPERATOR_TYPE_DELETEINSTANCE, memo);
        }
        delByRunId(processRun.getRunId());
    }

    /**
     * 获取流程扩展的根流程扩展（最顶层的父流程扩展）
     *
     * @param processRun 流程运行对象
     * @return 父流程运行对象
     */
    private ProcessRun getRootProcessRun(ProcessRun processRun) {
        ProcessInstance parentProcessInstance = runtimeService.createProcessInstanceQuery().subProcessInstanceId(processRun.getProcInstId()).singleResult();
        if (parentProcessInstance != null) {
            // Get parent ProcessRun
            ProcessRun parentProcessRun = getByProcInstanceId(parentProcessInstance.getProcessInstanceId());
            // Get Parent ProcessInstance sub ProcessInstance
            return getRootProcessRun(parentProcessRun);
        }
        return processRun;
    }

    /**
     * 批量删除草稿
     *
     * @param runIdAry id数组
     * @throws Exception 异常
     */
    @Transactional(rollbackFor = Exception.class)
    public void delDraft(String[] runIdAry) throws Exception {
        for (String id : runIdAry) {
            delDraft(id);
        }
    }

    /**
     * 删除草稿
     *
     * @param runId 流程运行id
     */
    @Transactional(rollbackFor = Exception.class)
    public void delDraft(String runId) throws Exception {
        ProcessRun processRun = getById(runId);
        String dsAlias = processRun.getDsAlias();
        String tableName = processRun.getTableName();
        String businessKey = processRun.getBusinessKey();
        if (StringUtil.isNotEmpty(tableName)) {
            if (StringUtil.isEmpty(dsAlias) || DataSourceUtil.DEFAULT_DATASOURCE.equals(dsAlias)) {
                if (!tableName.startsWith(TableModel.CUSTOMER_TABLE_PREFIX)) {
                    tableName = TableModel.CUSTOMER_TABLE_PREFIX + tableName;
                }
                bpmFormHandlerService.delByIdTableName(businessKey, tableName);
            } else {
                bpmFormHandlerService.delByDsAliasAndTableName(dsAlias, tableName, businessKey);
            }
        }
        bpmRunLogService.addRunLog(runId, BpmRunLog.OPERATOR_TYPE_DELETEFORM, "删除草稿");
        delById(runId);
    }

    /**
     * 根据runid找到copyid
     *
     * @return
     */
    public ProcessRun getCopyIdByRunid(String runId) {
        return processRunDao.getCopyIdByRunid(runId);
    }

    /**
     * 启动流程。
     *
     * <pre>
     * 	1.启动流程没有主键创建一个主键。
     * 	2.如果有关联的实例ID，也创建一个新的主键。
     * </pre>
     *
     * @param processCmd 流程命令执行对象
     * @return 流程实例
     */
    private ProcessInstance startWorkFlow(ProcessCmd processCmd) {
        String businessKey = processCmd.getBusinessKey();
        String userId = ContextUtil.getCurrentUserId();
        processCmd.addVariable(BpmConst.StartUser, userId);
        // 开始节点，在多个开始节点的情况下有值
        String startNode = processCmd.getStartNode();
        // 取得关联运行ID。
        String relRunId = processCmd.getRelRunId();
        ProcessInstance processInstance;
        if (StringUtil.isNotEmpty(businessKey)) {
            processCmd.addVariable(BpmConst.FLOW_BUSINESSKEY, businessKey);
        }
        // 如果主键为空，那么生成一个业务主键。
        // 如果传入了关联的relRunId实例ID
        // 那么启动流程是产生一个唯一的ID，避免流程引擎冲突。
        if (StringUtil.isEmpty(businessKey) || (BeanUtils.isNotIncZeroEmpty(relRunId))) {
            businessKey = String.valueOf(UUIDUtils.getUUIDFor32());
        }

        // 设置流程变量[startUser]。
        Authentication.setAuthenticatedUserId(userId);
        if (processCmd.getProcDefId() != null) {
            // 起始节点走向指定的第一个节点
            if (StringUtil.isNotEmpty(startNode)) {
                bpmService.reDrawLink(processCmd.getProcDefId(), startNode);
            }
            processInstance = bpmService.startFlowById(processCmd.getProcDefId(), businessKey, processCmd.getVariables());
        } else {
            // 起始节点走向指定的第一个节点
            if (StringUtil.isNotEmpty(startNode)) {
                ActDefModel actDefModel = actDefModelDao.getMainByModelKey(processCmd.getModelKey());
                if (BeanUtils.isNotEmpty(actDefModel) && StringUtil.isNotEmpty(actDefModel.getProcDefId())) {
                    bpmService.reDrawLink(actDefModel.getProcDefId(), startNode);
                }
            }
            processInstance = bpmService.startFlowByKey(processCmd.getModelKey(), businessKey, processCmd.getVariables());
        }
        Authentication.setAuthenticatedUserId(null);
        return processInstance;
    }

    /**
     * 添加ProcessRun
     *
     * @param entity 流程运行对象
     */
    public int add(ProcessRun entity) {
        processRunDao.add(entity);
        ProcessRun history = (ProcessRun) entity.clone();
        this.addHistory(history);
        return 0;
    }

    /**
     * 添加流程运行实例历史记录
     *
     * @param entity 流程运行对象
     */
    public void addHistory(ProcessRun entity) {
        SysUser curUser = (SysUser) ContextUtil.getCurrentUser();
        String tenantId = curUser.getTenantId();
        entity.setTenantId(tenantId);
        entity.setCreateBy(curUser.getUserId());
        entity.setUpdateBy(curUser.getUserId());
        if (entity.getCreateTime() == null) {
            entity.setCreateTime(new Date());
        }
        if (entity.getUpdateTime() == null) {
            entity.setUpdateTime(new Date());
        }
        processRunDao.addHistory(entity);
    }

    /**
     * 流程扩展更新
     *
     * @param entity 流程运行对象
     */
    public int update(ProcessRun entity) {
        ProcessRun history = (ProcessRun) entity.clone();
        if (ProcessRun.STATUS_MANUAL_FINISH.equals(entity.getStatus()) || ProcessRun.STATUS_FINISH.equals(entity.getStatus())) {
            Date endDate = new Date();
            Date startDate = history.getCreateTime();
            long duration = endDate.getTime() - startDate.getTime();
            history.setDuration(duration);
            history.setEndTime(endDate);
            this.updateHistory(history);
            processRunDao.delById(entity.getRunId());
        } else {
            this.updateHistory(history);
            processRunDao.updateProcessRun(entity);
        }
        return 0;
    }

    /**
     * 更新对象。
     */
    public void updateHistory(ProcessRun entity) {
        entity.setUpdateTime(new Date());
        entity.setUpdateBy(ContextUtil.getCurrentUserId());
        processRunDao.updateHistory(entity);
    }

    /**
     * 处理任务跳转。
     *
     * @param processCmd 流程命令执行对象
     * @throws Exception 异常
     */
    private void handJumpOverFirstNode(ProcessCmd processCmd) throws Exception {
        List<Task> taskList = TaskThreadService.getNewTasks();
        TaskThreadService.clearNewTasks();
        String curNode = "";
        TaskEntity curTask = null;
        for (Task taskEntity : taskList) {
            if (taskEntity.getProcessDefinitionId().equals(processCmd.getProcDefId())) {
                curTask = (TaskEntity) taskEntity;
                curNode = taskEntity.getTaskDefinitionKey();
            }
        }
        // 填写第一步意见。
        processCmd.addVariable(BpmConst.NODE_APPROVAL_STATUS + "_" + curNode, TaskOpinion.STATUS_SUBMIT);
        processCmd.addVariable(BpmConst.NODE_APPROVAL_CONTENT + "_" + curNode, "填写表单");
        processCmd.setVoteAgree(TaskOpinion.STATUS_SUBMIT);

        // 设置流程变量。
        setVariables(curTask, processCmd);

        skipTask(curTask, processCmd);

    }

    /**
     * 添加提交意见。
     *
     * @param processRun 流程运行对象
     * @param processCmd 流程命令执行对象
     */
    private void addSubmitOpinion(ProcessRun processRun, ProcessCmd processCmd) {
        TaskOpinion opinion = new TaskOpinion();
        String startUserId = processRun.getCreatorId();
        SysUser startUser = sysUserService.getOneById(startUserId);
        opinion.setOpinionId(UUIDUtils.getUUIDFor32());
        opinion.setCheckStatus(TaskOpinion.STATUS_SUBMIT);
        opinion.setProcInstId(processRun.getProcInstId());
        opinion.setExeFullname(startUser.getFullName());
        opinion.setExeUserId(startUserId);
        opinion.setTaskName("提交流程");
        opinion.setStartTime(processRun.getCreateTime());
        String content = "提交";
        if (BeanUtils.isNotEmpty(processCmd.getRelRunId()) && StringUtil.isNotEmpty(processCmd.getVoteContent())) {
            content = processCmd.getVoteContent();
        }
        opinion.setOpinion(content);
        opinion.setEndTime(new Date());
        opinion.setDurTime(0L);
        taskOpinionService.add(opinion);
    }

    /**
     * 判断是否允许追回，这个允许多实例撤销
     *
     * @param runId 流程运行id
     * @return 结果封装
     * @throws Exception 异常
     */
    public ResultData checkRecoverByStart(String runId) throws Exception {
        ProcessRun curProcessRun = this.getById(runId);
        ProcessRun processRun = this.getRootProcessRun(curProcessRun);
        ActDefModel actDefModel = actDefModelDao.getByProcDefId(processRun.getProcDefId());
        // 判断当前人是否为发起人。
        // 如果当前人为发起人那么可以撤回所有的流程。
        boolean isCreator = processRun.getCreatorId().equals(ContextUtil.getCurrentUserId());
        if (!isCreator) {
            return ResultData.error("process.can.recover.toStart.only.by.startUser");
        }
        Short status = processRun.getStatus();
        if (status.shortValue() == ProcessRun.STATUS_FINISH) {
            return ResultData.error("process.already.end");
        }

        if (status.shortValue() == ProcessRun.STATUS_MANUAL_FINISH) {
            return ResultData.error("process.already.deleted");
        }
        if (ActDefModel.STATUS_INST_DISABLED.equals(actDefModel.getStatus())) {
            return ResultData.error("model.instance.is.forbidden");
        }

        String nodeId;
        // 判断初始节点后是否有多条路径
        if (NodeCache.isMultipleFirstNode(processRun.getProcDefId())) {
            nodeId = processRun.getStartNode();
        } else {
            FlowNodeInfo flowNodeInfo = NodeCache.getFirstNodeInfo(processRun.getProcDefId());
            if (flowNodeInfo == null) {
                return ResultData.error("process.can.not.find.startNode");
            }
            nodeId = flowNodeInfo.getNodeId();
        }
        List<ProcessTask> taskList = bpmService.getTasks(processRun.getProcInstId());

        List<String> taskNodeIdList = getNodeIdByTaskList(taskList);
        if (taskNodeIdList.contains(nodeId)) {
            if (status.shortValue() == ProcessRun.STATUS_RUNNING || status.shortValue() == ProcessRun.FORMAL_RUNNING || status.shortValue() == ProcessRun.RECOVER) {
                return ResultData.error("process.is.on.startNode");
            }
            return ResultData.error("process.already.recovered!");
        }

        return ResultData.success("process.can.recover");
    }

    /**
     * 获取所有任务节点的id
     *
     * @param taskList 任务列表
     * @return 节点列表
     */
    private List<String> getNodeIdByTaskList(List<ProcessTask> taskList) {
        List<String> list = new ArrayList<>();
        for (ProcessTask task : taskList) {
            list.add(task.getTaskDefinitionKey());
        }
        return list;
    }

    /**
     * 追回任务
     *
     * @param runId      流程运行id
     * @param informType 消息类型
     * @param memo       原因
     * @return 结果封装
     * @throws Exception 异常
     */
    @Transactional(rollbackFor = Exception.class)
    public String redo(String runId, String informType, String memo) throws Exception {

        //根据runId获取流程实例
        ProcessRun processRun = this.getById(runId);
        //获取流程实例id
        String procInstId = processRun.getProcInstId();
        //获取流程扩展的根流程扩展（最顶层的父流程扩展）
        ProcessRun rootProcessRun = getRootProcessRun(processRun);
        String subject = rootProcessRun.getSubject();

        // 获取开始节点。
        FlowNodeInfo flowNodeInfo = null;
        // 判断初始节点后是否有多条路径
        if (NodeCache.isMultipleFirstNode(rootProcessRun.getProcDefId())) {
            flowNodeInfo = NodeCache.getNodeByActNodeId(rootProcessRun.getProcDefId(), rootProcessRun.getStartNode());
        } else {
            flowNodeInfo = NodeCache.getFirstNodeInfo(rootProcessRun.getProcDefId());
        }

        // 主要为了找到子流程的实例ID
        Set<String> instSet = taskOpinionService.getInstListByInstId(procInstId);
        List<ProcessTask> tasks = new ArrayList<>();
        for (String id : instSet) {
            tasks.addAll(bpmService.getTasks(id));
        }

        List<Task> taskList = new ArrayList<>();
        for (ProcessTask task : tasks) {
            taskList.add(bpmService.getTask(task.getId()));
            // 更新审批意见状态。
            updOpinionByTaskId(task.getId());
        }
        // 任务追回 通知任务所属人
        Map<String, String> map = this.getTempByUseType(SysTemplate.USE_TYPE_REVOKED);
        taskMessageService.notify(taskList, informType, subject, map, memo, "");

        // 添加到流程运行日志
        /*2019-11-14   完善追回按钮的操作日志*/
        if (BeanUtils.isNotEmpty(taskList)) {
            bpmRunLogService.addRunLog(runId, BpmRunLog.OPERATOR_TYPE_REDO, "用户在节点【" + taskList.get(0).getName() + "】执行了追回操作。");
        } else {
            bpmRunLogService.addRunLog(runId, BpmRunLog.OPERATOR_TYPE_REDO, "用户执行了追回操作。");
        }

        // 删除关联的流程变量
        delRelateVars(procInstId);
        // 手动删除分发汇总流程的关联的流程变量
        executionDao.delVarByInstIdAndVarName(procInstId, "_token_");
        // 删除分发汇总流程的令牌
        taskForkDao.delByProcInstId(procInstId);

        ProcessInstance parentProcessInstance = runtimeService.createProcessInstanceQuery().processInstanceId(rootProcessRun.getProcInstId()).singleResult();
        if (parentProcessInstance != null) {
            if (flowNodeInfo == null) {
                return "operation.failure";
            }
            addTaskInRedo(parentProcessInstance, flowNodeInfo);
            addRedoOpinion(rootProcessRun, memo);
            ProcessRun historyProcessRun = processRunDao.getByIdHistory(rootProcessRun.getRunId());
            rootProcessRun.setStatus(ProcessRun.STATUS_REDO);
            historyProcessRun.setStatus(ProcessRun.STATUS_REDO);
            processRunDao.updateHistory(historyProcessRun);
            processRunDao.updateProcessRun(rootProcessRun);

        }
        return null;
    }

    /**
     * 更新审批意见状态。
     */
    private void updOpinionByTaskId(String taskId) {
        SysUser curUser = (SysUser) ContextUtil.getCurrentUser();
        TaskOpinion opinion = taskOpinionDao.getByTaskId(taskId);
        opinion.setCheckStatus(TaskOpinion.STATUS_RETRIEVE);
        opinion.setEndTime(new Date());
        opinion.setExeUserId(curUser.getUserId());
        opinion.setExeFullname(curUser.getFullName());
        opinion.setDurTime(System.currentTimeMillis() - opinion.getStartTime().getTime());
        taskOpinionDao.update(opinion);
    }

    /**
     * 根据用户类型获取消息模板
     *
     * @param useType 用户类型
     * @return 消息模板集合
     * @throws Exception 异常
     */
    private Map<String, String> getTempByUseType(Integer useType) throws Exception {
        SysTemplate temp = sysTemplateService.getDefaultByUseType(useType);
        if (temp == null) {
            throw new Exception("模板中未找到内部消息的默认模板或系统模板");
        }
        Map<String, String> map = new HashMap<>(3);
        map.put(SysTemplate.TEMPLATE_TITLE, temp.getTitle());
        map.put(SysTemplate.TEMPLATE_TYPE_HTML, temp.getHtmlContent());
        map.put(SysTemplate.TEMPLATE_TYPE_PLAIN, temp.getPlainContent());

        return map;
    }

    /**
     * 删除关联的流程变量
     */
    private void delRelateVars(String procInstId) {
        Map<String, Object> varsMap = runtimeService.getVariables(procInstId);
        for (String name : varsMap.keySet()) {
            if (!containVars(name)) {
                continue;
            }
            executionDao.delVarByInstIdAndVarName(procInstId, name);
        }
    }

    /**
     * 判断字符串中是否包含指定字符串
     *
     * @param varName 变量字符串
     * @return 判定结果
     */
    private boolean containVars(String varName) {
        String[] aryNames = new String[]{"nrOfInstances", "nrOfCompletedInstances", "nrOfActiveInstances", "loopCounter", "_signUsers"};
        for (String tmp : aryNames) {
            if (varName.contains(tmp)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 描述: 在追回中重新分配任务
     *
     * @param parentProcessInstance 流程实例
     * @param flowNodeInfo          开始节点
     * @author wzh
     */
    private void addTaskInRedo(ProcessInstance parentProcessInstance, FlowNodeInfo flowNodeInfo) {
        String procInstId = parentProcessInstance.getProcessInstanceId();
        // 删除非主线程相关信息
        delSubInfoByProcInstId(procInstId);

        Date curr = new Date();

        //20191129 jyq新增，目的是新创建一个execution，因为flowable有一条实例，一个执行流
        String newExecutionId = UUIDUtils.getUUIDFor32();
        ProcessExecution newExecution = new ProcessExecution();
        newExecution.setId(newExecutionId);
        newExecution.setProcessInstanceId(procInstId);
        newExecution.setParentId(procInstId);
        newExecution.setProcessDefinitionId(parentProcessInstance.getProcessDefinitionId());
        newExecution.setRootProcInstId(procInstId);
        newExecution.setActivityId(flowNodeInfo.getNodeId());
        newExecution.setActive(true);
        newExecution.setConcurrent(false);
        newExecution.setScope(false);
        newExecution.setSuspensionState(1);
        newExecution.setStartTime(curr);
        newExecution.setTaskCount(1);

        String newTaskId = UUIDUtils.getUUIDFor32();
        String currentId = ContextUtil.getCurrentUserId();
        String defId = parentProcessInstance.getProcessDefinitionId();
        // 创建新的任务给发起人
        ProcessTask newTask = new ProcessTask();
        newTask.setId(newTaskId);
        newTask.setProcessInstanceId(procInstId);
        newTask.setProcessDefinitionId(defId);
        newTask.setExecutionId(newExecutionId);
        newTask.setName(flowNodeInfo.getNodeName());
        newTask.setTaskDefinitionKey(flowNodeInfo.getNodeId());
        newTask.setPriority(TaskEntity.DEFAULT_PRIORITY);
        newTask.setCreateTime(curr);
        newTask.setAssignee(currentId);
        newTask.setOwner(currentId);
        newTask.setDescription("-1");
        // 创建新的历史
        ProcessTaskHistory newTaskHistory = new ProcessTaskHistory();
        newTaskHistory.setId(newTaskId);
        newTaskHistory.setProcessDefinitionId(defId);
        newTaskHistory.setExecutionId(newExecutionId);
        newTaskHistory.setName(flowNodeInfo.getNodeName());
        newTaskHistory.setTaskDefinitionKey(flowNodeInfo.getNodeId());
        newTaskHistory.setStartTime(curr);
        newTaskHistory.setAssignee(currentId);
        newTaskHistory.setOwner(currentId);
        newTaskHistory.setDescription("-1");
        newTaskHistory.setStartTime(curr);

        BpmProStatus bpmProStatus = new BpmProStatus();
        bpmProStatus.setId(newTaskId);
        bpmProStatus.setProcInstId(procInstId);
        bpmProStatus.setLastUpdateTime(curr);
        bpmProStatus.setNodeId(flowNodeInfo.getNodeId());
        bpmProStatus.setNodeName(flowNodeInfo.getNodeName());
        bpmProStatus.setProcDefId(defId);
        bpmProStatus.setStatus(TaskOpinion.STATUS_RESUBMIT);

        //20191129 jyq修改，新增执行流
        executionDao.add(newExecution);
        taskDao.insertTask(newTask);
        taskHistoryDao.add(newTaskHistory);
        bpmProStatusDao.add(bpmProStatus);
    }

    /**
     * 删除子实例及相关信息
     *
     * @param procInstId 流程实例id
     */
    private void delSubInfoByProcInstId(String procInstId) {
        // 获取子流程列表
        List<ProcessExecution> processExecutionList = executionDao.getSubExecutionByProcInstId(procInstId);
        for (ProcessExecution execution : processExecutionList) {
            String subProcInstId = execution.getId();
            // 如果不是活动，可能包含多实例的子流程
            if (!execution.isActive()) {
                delSubInfoByProcInstId(subProcInstId);
            } else {
                List<ProcessExecution> subProcessExecutionList = executionDao.getSubExecutionBySuperId(subProcInstId);
                if (subProcessExecutionList != null && subProcessExecutionList.size() == 1) {
                    ProcessExecution subProcessExecution = subProcessExecutionList.get(0);

                    String subProcinstid = subProcessExecution.getProcessInstanceId();
                    delSubInfoByProcInstId(subProcinstid);
                    // 删除子流程根实例
                    bpmProStatusDao.delByProcInstId(subProcinstid);
                    processRunDao.delActHiProcessInstanceByProcInstId(subProcinstid);
                    processRunDao.delHistoryByprocInstId(subProcinstid);
                    processRunDao.delProByProcInstId(subProcinstid);
                    taskDao.delByInstanceId(subProcinstid);
                    executionDao.delVariableByProcInstId(subProcinstid);
                    executionDao.delExecutionById(subProcinstid);
                }
            }
            processRunDao.delActHiProcessInstanceByProcInstId(subProcInstId);
            processRunDao.delHistoryByprocInstId(subProcInstId);
            processRunDao.delProByProcInstId(subProcInstId);
            executionDao.delVariableByProcInstId(subProcInstId);
            // 删除task表数据
            taskDao.delByInstanceId(subProcInstId);
            executionDao.delExecutionByProcInstId(subProcInstId);

        }
        // 删除审批相关信息
        bpmProStatusDao.delByProcInstId(procInstId);
        // 根据实例Id删除子流程历史
        processRunDao.delSubHistoryByProcInstId(procInstId);
        // 根据流程实例Id删除非主线程的流程
        processRunDao.delSubProByProcInstId(procInstId);
        // 根据实例Id删除act子流程历史
        processRunDao.delSubActHiProcessInstanceByProcInstId(procInstId);
        taskDao.delCandidateByInstanceId(procInstId);
        // 根据流程实例删除任务。
        taskDao.delByInstanceId(procInstId);
        executionDao.delSubExecutionByProcInstId(procInstId);
        executionDao.delSubVarByProcInstId(procInstId);
    }

    /**
     * 描述: 添加追回原因
     *
     * @param processRun 流程运行对象
     * @param memo       原因
     * @author wzh
     */
    private void addRedoOpinion(ProcessRun processRun, String memo) {
        TaskOpinion opinion = new TaskOpinion();
        String startUserId = processRun.getCreatorId();
        SysUser startUser = sysUserService.getOneById(startUserId);
        opinion.setOpinionId(UUIDUtils.getUUIDFor32());
        opinion.setCheckStatus(TaskOpinion.STATUS_RETRIEVE);
        opinion.setProcInstId(processRun.getProcInstId());
        opinion.setExeFullname(startUser.getFullName());
        opinion.setExeUserId(startUserId);
        opinion.setOpinion(memo);
        opinion.setStartTime(processRun.getCreateTime());
        opinion.setTaskName("追回流程");
        opinion.setEndTime(new Date());
        opinion.setDurTime(0L);
        taskOpinionService.add(opinion);
    }

    /**
     * 获取授权后的流程分类
     *
     * @param hasRoot 是否有根节点
     * @return List<GlobalType>
     */
    public List<GlobalType> getAuthorizedProcessType(String catKey, boolean hasRoot) {
        String userId = ContextUtil.getCurrentUserId();
        String tenantId = ContextUtil.getCurrentUser().getTenantId();
        Map<String, Object> actRightMap = bpmDefAuthorizeService.getActRightsByUserMap(userId,
                BpmDefAuthorizeType.BPMDEFAUTHORIZE_RIGHT_TYPE.START, false, false);
        String actRights = (String) actRightMap.get("authorizeIds");
        List<GlobalType> list = globalTypeDao.getByTenantIdAndActRights(tenantId, actRights);
        //去重
        list = list.stream().collect(Collectors.collectingAndThen(
                Collectors.toCollection(
                        () -> new TreeSet<>(Comparator.comparing(GlobalType::getTypeId))),
                ArrayList::new));

        //是否有根节点
        if (hasRoot) {
            SysTypeKey sysTypeKey = sysTypeKeyDao.getSysTypeKeyByKey(catKey);
            GlobalType globalType = new GlobalType();
            globalType.setTypeName(sysTypeKey.getTypeName());
            globalType.setCatKey(sysTypeKey.getTypeKey());
            globalType.setParentId("0");
            globalType.setIsParent("true");
            globalType.setType(sysTypeKey.getType());
            globalType.setTypeId(sysTypeKey.getTypeId());
            globalType.setNodePath(sysTypeKey.getTypeId() + ".");
            list.add(0, globalType);
        }
        return list;
    }

    /**
     * 获取父流程定义ID
     *
     * @param processRun 流程运行对象
     * @return 父流程定义id
     * @author zyl
     */
    public String getParentProcessRunProcDefId(ProcessRun processRun) {
        if (BeanUtils.isNotEmpty(processRun) && StringUtil.isNotEmpty(processRun.getParentId())) {
            ProcessRun parentProcessRun = getById(processRun.getParentId());
            if (BeanUtils.isNotEmpty(parentProcessRun)) {
                return parentProcessRun.getProcDefId();
            }
        }
        return "";
    }

    /**
     * 姚陆洋
     *
     * @param taskId 节点的Id
     * @return 任务
     */
    public ProcessTask getByTaskId(String taskId) {
        return taskDao.getByTaskId(taskId);
    }

    /**
     * 判断是否允许撤销
     *
     * @param runId 流程运行id
     * @return 结果集封装
     * @throws Exception 异常
     */
    public String checkRecover(String runId) throws Exception {
        ProcessRun processRun = this.getById(runId);
        if (processRun.getParentId() != null && !processRun.getParentId().isEmpty()) {
            processRun = this.getById(processRun.getParentId());
        }
        ActDefModel actDefModel = actDefModelDao.getByProcDefId(processRun.getProcDefId());
        if (ActDefModel.STATUS_INST_DISABLED.equals(actDefModel.getStatus())) {
            return "model.instance.is.forbidden";
        }
        String procInstId = processRun.getProcInstId();
        List<ProcessTask> taskList = bpmService.getTasks(procInstId);
        ProcessTaskHistory taskEntity;
        List<ProcessTaskHistory> taskEntityList = taskHistoryDao.getLastFinishTaskByProcId(taskList.get(0).getProcessInstanceId());
        if (BeanUtils.isNotEmpty(taskEntityList)) {
            taskEntity = taskEntityList.get(0);
        } else {
            // 下一节点为子流程
            return "process.next.isOut.failure";
        }
        return isCanRecover(processRun, taskEntity, taskList);
    }

    /**
     * 是否可以追回
     *
     * @param processRun        流程运行对象
     * @param processTaskEntity 任务
     * @param taskList          任务列表
     * @return 判定结果
     * @throws Exception 异常
     */
    private String isCanRecover(ProcessRun processRun, ProcessTaskHistory processTaskEntity, List<ProcessTask> taskList) {
        Short status = processRun.getStatus();
        if (status.shortValue() == ProcessRun.STATUS_FINISH) {
            return "process.already.end";
        }
        if (status.shortValue() == ProcessRun.STATUS_MANUAL_FINISH) {
            return "process.already.deleted";
        }
        HistoricTaskInstanceEntity hisTaskEntity = bpmService.getHistoricTaskInstanceEntity(processTaskEntity.getId());
        TaskEntityImpl taskEntityImpl = new TaskEntityImpl();
        BeanUtils.copyNotNullProperties(taskEntityImpl, hisTaskEntity);
        if (bpmService.isSignTask(taskEntityImpl.getProcessDefinitionId(), taskEntityImpl.getTaskDefinitionKey())) {
            return "singTask.can.not.redo";
        }

        if (!ContextUtil.getCurrentUserId().equals(processTaskEntity.getAssignee())) {
            return "you.are.not.the.executor";
        }

        boolean hasRead = getTaskHasRead(taskList);
        if (hasRead) {
            return "task.is.already.read";
        }
        // 判断是否允许返回。
        boolean allowBack = getTaskAllowBack(taskList);
        if (!allowBack) {
            return "instance.can.not.redo";
        }
        return null;
    }

    /**
     * 根据任务列表是否已读。
     *
     * @param list 任务列表
     * @return 判定结果
     */
    private boolean getTaskHasRead(List<ProcessTask> list) {
        boolean rtn = false;
        for (ProcessTask task : list) {
            List<TaskRead> readList = taskReadDao.getTaskRead(task.getProcessInstanceId(), task.getId());
            if (BeanUtils.isNotEmpty(readList)) {
                rtn = true;
                break;
            }
        }
        return rtn;
    }

    /**
     * 取得任务列表是否允许撤销。
     *
     * @param list 任务列表
     * @return 判定结果
     */
    private boolean getTaskAllowBack(List<ProcessTask> list) {
        for (ProcessTask task : list) {
            boolean allBack = bpmFlowableService.isTaskAllowBack(task.getId());
            if (allBack) {
                return true;
            }
        }
        return false;

    }

    /**
     * 获取流程执行持续持剑
     *
     * @param entity 流程运行对象
     * @return 执行持续时间
     */
    public Long getProcessDuration(ProcessRun entity) {
        Long duration = 0L;


        String procInstId = entity.getProcInstId();
        Map<String, Map<Long, List<TaskOpinion>>> signTask = new HashMap<>(16);

        Map<String, Object> params = new HashMap<>(1);
        params.put("procInstId", entity.getProcInstId());

        List<TaskOpinion> taskOpinions = taskOpinionService.getByProcInstId(procInstId);

        for (TaskOpinion opn : taskOpinions) {
            boolean isSignTask = bpmService.isSignTask(entity.getProcDefId(), opn.getTaskKey());
            if (!isSignTask) {
                // 不是会签
                if (BeanUtils.isNotEmpty(opn.getDurTime())) {
                    Long durTime = opn.getDurTime();
                    duration += durTime;
                }

            } else {
                // 是会签，按批次，取最大值
                Map<Long, List<TaskOpinion>> taskMap = signTask.computeIfAbsent(opn.getTaskKey(), k -> new HashMap<>(16));
                if (opn.getTaskId() == null) {
                    continue;
                }
                TaskSignData signData = taskSignDataService.getByTaskId(opn.getTaskId());
                if (signData == null) {
                    continue;
                }
                List<TaskOpinion> taskList = taskMap.computeIfAbsent(signData.getGroupNo(), k -> new ArrayList<>());
                taskList.add(opn);
            }
        }

        //添加会签的节点时长
        duration = getSingNodeDuration(duration, signTask);

        return duration;
    }

    /**
     * 获得上一次提交的更新时间
     *
     * @param entity 任务
     * @return 持续时间
     */
    public Long getProcessLastSubmitDuration(ProcessRun entity) {
        Long duration = 0L;
        String actInstId = entity.getProcInstId();

        Map<String, Map<Long, List<TaskOpinion>>> signTask = new HashMap<>(16);

        Map<String, Object> params = new HashMap<>(1);
        params.put("actInstId", entity.getProcInstId());

        List<TaskOpinion> taskOpinions = taskOpinionService.getByProcInstId(actInstId);
        List<TaskOpinion> lastTaskOpinions = new ArrayList<>();
        // 以结束时间排序
        taskOpinions.sort((o1, o2) -> {
            Date startTime1 = o1.getStartTime();
            Date endTime1 = o1.getEndTime();
            Date startTime2 = o2.getStartTime();
            Date endTime2 = o2.getEndTime();

            if (endTime1 != null && endTime2 != null) {
                return endTime1.compareTo(endTime2);
            } else if (endTime1 != null) {
                return 1;
            } else if (endTime2 != null) {
                return -1;
            } else {
                return startTime1.compareTo(startTime2);
            }
        });

        boolean hasResubmit = false;

        for (int i = taskOpinions.size() - 1; i >= 0; i--) {
            if (TaskOpinion.STATUS_RESUBMIT.equals(taskOpinions.get(i).getCheckStatus())) {
                hasResubmit = true;
                break;
            } else {
                lastTaskOpinions.add(taskOpinions.get(i));
            }
        }

        if (!hasResubmit) {
            if (entity.getDuration() != null) {
                return entity.getDuration();
            } else {
                return getProcessDuration(entity);
            }
        }

        for (TaskOpinion opn : lastTaskOpinions) {
            boolean isSignTask = bpmService.isSignTask(entity.getProcDefId(), opn.getTaskKey());
            if (!isSignTask) {
                // 不是会签
                if (opn.getDurTime() != null) {
                    duration += opn.getDurTime();
                }
            } else {
                // 是会签，按批次，取最大值
                Map<Long, List<TaskOpinion>> taskMap = signTask.computeIfAbsent(opn.getTaskKey(), k -> new HashMap<>(16));
                TaskSignData signData = taskSignDataService.getByTaskId(opn.getTaskId());
                List<TaskOpinion> taskList = taskMap.computeIfAbsent(signData.getGroupNo(), k -> new ArrayList<>());
                taskList.add(opn);
            }
        }

        // 添加会签的节点时长
        duration = getSingNodeDuration(duration, signTask);

        return duration;
    }

    /**
     * 获取会签的节点市场
     *
     * @param duration 持续时长
     * @param signTask 任务
     * @return
     */
    private Long getSingNodeDuration(Long duration, Map<String, Map<Long, List<TaskOpinion>>> signTask) {
        for (Map.Entry<String, Map<Long, List<TaskOpinion>>> entry : signTask.entrySet()) {
            Map<Long, List<TaskOpinion>> map = entry.getValue();
            for (Map.Entry<Long, List<TaskOpinion>> ent : map.entrySet()) {
                Long maxDuration = 0L;
                List<TaskOpinion> list = ent.getValue();
                for (TaskOpinion o : list) {
                    long durtime = o.getDurTime() == null ? 0 : o.getDurTime();
                    if (maxDuration > durtime) {
                        maxDuration = durtime;
                    }
                }
                duration += maxDuration;
            }
        }
        return duration;
    }

    /**
     * 根据流程实例对流程进行撤销
     *
     * @param runId      流程运行id
     * @param informType 消息类型
     * @param memo       原因
     * @return 结果集封装
     * @throws Exception 异常
     */
    @Transactional(rollbackFor = Exception.class)
    public String recover(String runId, String informType, String memo) throws Exception {
        ProcessRun processRun = this.getById(runId);
        List<ProcessTask> taskList = bpmService.getTasks(processRun.getProcInstId());
        ProcessTaskHistory taskEntity = null;
        List<ProcessTaskHistory> taskEntityList = taskHistoryDao.getLastFinishTaskByProcId(taskList.get(0).getProcessInstanceId());
        if (BeanUtils.isNotEmpty(taskEntityList)) {
            taskEntity = taskEntityList.get(0);
        }
        String msg = isCanRecover(processRun, taskEntity, taskList);
        if (msg != null) {
            return msg;
        }
        ExecutionStack curTaskExecution = executionStackService.getLastestStack(taskEntity.getProcessInstanceId(), taskEntity.getTaskDefinitionKey());
        for (int i = 0; i < taskList.size(); i++) {
            ProcessTask processTask = taskList.get(i);
            ExecutionStack taskExecution = executionStackService.getLastestStack(processTask.getProcessInstanceId(), processTask.getTaskDefinitionKey());
            // 找到需要撤销的任务，过滤由当前节点的产生的任务。
            if (taskExecution != null && taskExecution.getParentId().equals(curTaskExecution.getStackId())) {
                // 加上回退状态，使其通过任务完成事件中记录其是撤销的状态
                ProcessCmd processCmd = new ProcessCmd();
                processCmd.setTaskId(processTask.getId());
                // 设置为撤销
                processCmd.setRecover(true);
                processCmd.setBack(BpmConst.TASK_BACK);
                processCmd.setVoteAgree(TaskOpinion.STATUS_RECOVER);
                processCmd.setVoteContent(memo);
                processCmd.setInformType(informType);
                // 第一个任务跳转回来，其他直接完成任务。
                if (i > 0) {
                    processCmd.setOnlyCompleteTask(true);
                }
                // 进行回退处理
                this.nextProcess(processCmd);
                // 判断是否为别人转办的任务 如果是从交办记录表中标记为取消记录
                bpmTaskExeService.cancel(processTask.getId());
            }
        }
        return null;
    }

    /**
     * 保存表单数据
     *
     * @param processCmd 流程命令执行对象
     */
    public void saveFlowData(ProcessCmd processCmd) throws Exception {
        String taskId = processCmd.getTaskId();
        ProcessRun processRun;
        BpmNodeSet bpmNodeSet;
        String procDefId = processCmd.getProcDefId();
        if (StringUtil.isEmpty(taskId)) {
            processRun = processCmd.getProcessRun();
            bpmNodeSet = getStartBpmNodeSet("", procDefId);
        } else {
            TaskEntity task = bpmService.getTask(taskId);
            String nodeId = task.getTaskDefinitionKey();
            String procInstId = task.getProcessInstanceId();
            processRun = getByProcInstanceId(procInstId);
            String opinion = processCmd.getVoteContent();
            saveOpinion(task, opinion);
            String parentProcDefId = (String) taskService.getVariableLocal(taskId, BpmConst.FLOW_PARENT_PROCDEFID);
            if (StringUtil.isEmpty(parentProcDefId)) {
                bpmNodeSet = bpmNodeSetService.getByProcDefIdNodeId(procDefId, nodeId, null);
            } else {
                bpmNodeSet = bpmNodeSetService.getByProcDefIdNodeId(procDefId, nodeId, parentProcDefId);
            }
            //在流程审批时，更新流程变量
            setVariables(task, processCmd);
        }
        //处理业务表单,外部调用不触发表单处理
        if (!processCmd.isInvokeExternal()) {
            handlerFormData(processCmd, processRun, bpmNodeSet.getNodeId());
        }
        // 调用前置处理器
        if (!processCmd.isSkipPreHandler()) {
            invokeHandler(processCmd, bpmNodeSet, true);
        }
    }

    /**
     * 保存沟通或流转意见，并删除任务。 设置流转的转办代理事宜为完成 删除被流转任务产生的沟通任务
     *
     * @param taskEntity  任务
     * @param taskOpinion 意见
     */
    public void saveOpinion(TaskEntity taskEntity, TaskOpinion taskOpinion) {
        String taskId = taskEntity.getId();
        String description = taskEntity.getDescription();
        // 处理审批意见的父流程ID
        this.dealTaskOpinSupId(taskEntity, taskOpinion);

        taskOpinionDao.add(taskOpinion);
        taskService.deleteTask(taskId);

        if (description.equals(TaskOpinion.STATUS_TRANSTO_ING.toString())) {
            this.handleInterveneTransTo(taskId);
        } else if (description.equals(TaskOpinion.STATUS_TRANSTO.toString())) {
            // 删除被流转任务产生的沟通任务
            taskDao.delCommuTaskByParentTaskId(taskId);
        }
    }

    /**
     * 保存任务处理意见。
     *
     * @param taskEntity 任务
     * @param opinion    意见
     */
    public void saveOpinion(TaskEntity taskEntity, String opinion) {
        String taskId = taskEntity.getId();
        SysUser curUser = (SysUser) ContextUtil.getCurrentUser();
        TaskOpinion taskOpinion = taskOpinionDao.getOpinionByTaskId(taskId, curUser.getUserId());
        if (taskOpinion == null) {
            String opinionId = UUIDUtils.getUUIDFor32();
            taskOpinion = new TaskOpinion();
            taskOpinion.setOpinionId(opinionId);
            taskOpinion.setProcDefId(taskEntity.getProcessDefinitionId());
            taskOpinion.setProcInstId(taskEntity.getProcessInstanceId());
            taskOpinion.setTaskId(taskId);
            taskOpinion.setTaskKey(taskEntity.getTaskDefinitionKey());
            taskOpinion.setTaskName(taskEntity.getName());
            taskOpinion.setStartTime(new Date());
            taskOpinion.setCheckStatus(TaskOpinion.STATUS_OPINION);
            taskOpinion.setOpinion(opinion);
            taskOpinion.setExeUserId(curUser.getUserId());
            taskOpinion.setExeFullname(curUser.getFullName());

            // 处理审批意见的父流程ID
            this.dealTaskOpinSupId(taskEntity, taskOpinion);

            taskOpinionDao.add(taskOpinion);
        } else {
            taskOpinion.setExeUserId(curUser.getUserId());
            taskOpinion.setExeFullname(curUser.getFullName());
            taskOpinion.setOpinion(opinion);
            taskOpinionDao.update(taskOpinion);
        }

    }

    /**
     * 处理审批意见时是否添加父流程Id，如果是外部子流程则添加，否则就不添加为空
     *
     * @param taskEntity  任务
     * @param taskOpinion 意见
     */
    private void dealTaskOpinSupId(TaskEntity taskEntity, TaskOpinion taskOpinion) {
        // 加签会签时，获取流转的主任务
        while (taskEntity.getExecutionId() == null) {
            String parentTaskId = taskEntity.getParentTaskId();
            taskEntity = bpmService.getTask(parentTaskId);
        }
        Object isExtCall = runtimeService.getVariable(taskEntity.getExecutionId(), BpmConst.IS_EXTERNAL_CALL);
        // 外部子流程做处理
        if (isExtCall != null) {
            // 处理如果是外部子流程，运行到外部子流程节点时，审批意见添加父流程的ID
            ProcessExecution processExecution = executionDao.getById(taskEntity.getProcessInstanceId());
            // 子流程获取主流程的实例ID
            if (StringUtil.isEmpty(processExecution.getSuperExecutionId())) {
                taskOpinion.setSuperExecution(processExecution.getId());
            } else {
                ProcessExecution superExecution = executionDao.getById(processExecution.getSuperExecutionId());
                taskOpinion.setSuperExecution(superExecution.getParentId());
            }
        }

    }

    /**
     * 办结和已办流程分类列表
     *
     * @param assignee 执行人
     * @param category 分类
     * @return 分类列表
     */
    public List<GlobalType> queryAlreadyMatterAndCompletedMattersTypeList(String assignee, String category) {
        List<GlobalType> list = processRunDao.queryAlreadyMatterAndCompletedMattersTypeList(assignee, category);
        SysTypeKey sysTypeKey = sysTypeKeyDao.getSysTypeKeyByKey(GlobalType.CAT_FLOW);
        GlobalType globalType = new GlobalType();
        globalType.setTypeName(sysTypeKey.getTypeName());
        globalType.setParentId("0");
        globalType.setIsParent("true");
        globalType.setTypeId(sysTypeKey.getTypeId());
        globalType.setType(sysTypeKey.getType());
        globalType.setNodePath(sysTypeKey.getTypeId() + ".");
        list.add(0, globalType);
        return list;
    }

    /**
     * 获取测试状态流程实例
     *
     * @param procDefId 流程定义id
     * @return 流程运行列表
     */
    public List<ProcessRun> queryTestRunByProcDefId(String procDefId) {
        return processRunDao.queryTestRunByProcDefId(procDefId);
    }

    /**
     * 获取父流程的实例ID
     *
     * @param procInstanceId 流程实例id
     * @return 父流程实id
     */
    public String getSuperProcInstId(String procInstanceId) {
        if (procInstanceId == null) {
            return null;
        }
        ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().subProcessInstanceId(procInstanceId).singleResult();
        if (processInstance == null) {
            return null;
        }
        return processInstance.getProcessInstanceId();
    }

    /**
     * 更改任务执行人
     *
     * @param taskId      任务id
     * @param userId      用户id
     * @param voteContent 审批意见
     * @param informType  消息类型
     */
    public String updateTaskAssignee(String taskId, String userId, String voteContent, String informType) {
        TaskEntity taskEntity = bpmService.getTask(taskId);
        String originUserId = taskEntity.getAssignee();
        if (BeanUtils.isEmpty(taskEntity)) {
            return "task.query.failure";
        }
        if (userId.equals(originUserId)) {
            return "task.assignee.same";
        }
        String procDefID = taskEntity.getProcessDefinitionId();
        ActDefModel actDefModel = actDefModelDao.getByProcDefId(procDefID);
        if (ActDefModel.STATUS_INST_DISABLED.equals(actDefModel.getStatus())) {
            return "task.inst.disabled";
        }
        SysUser user = sysUserService.getById(userId);
        SysUser curUser = (SysUser) ContextUtil.getCurrentUser();

        bpmService.updateTaskAssignee(taskId, userId);

        String originUserName = null;
        if (ServiceUtil.isAssigneeNotEmpty(originUserId)) {
            SysUser originUser = sysUserService.getById(originUserId);
            originUserName = originUser.getFullName();
        }

        //记录日志
        ProcessRun processRun = this.getByProcInstanceId(taskEntity.getProcessInstanceId());
        String runId = processRun.getRunId();
        StringBuffer memo = new StringBuffer("流程:" + processRun.getSubject() + ", 【" + curUser.getFullName() + "】将任务【" + taskEntity.getName() + "】");
        if (StringUtil.isNotEmpty(originUserName)) {
            memo.append(",原执行人为:【").append(originUserName).append("】,");
        }
        memo.append("现指派给【").append(user.getFullName()).append("】执行。");
        bpmRunLogService.addRunLog(runId, BpmRunLog.OPERATOR_TYPE_ASSIGN, memo.toString());

        memo = new StringBuffer("流程:" + processRun.getSubject() + ", 【" + curUser.getFullName() + "】将任务【" + taskEntity.getName() + "】 ");
        if (StringUtil.isNotEmpty(originUserName)) {
            memo.append(",原执行人为:【").append(originUserName).append("】,");
        }
        memo.append("指派给【").append(user.getFullName()).append("】执行。");
        bpmRunLogService.addRunLog(user, runId, BpmRunLog.OPERATOR_TYPE_ASSIGN, memo.toString());
        String content = memo + " 原因为：" + voteContent;

        TaskOpinion taskOpinion = taskOpinionService.getByTaskId(taskId);
        Short checkStatus = taskOpinion.getCheckStatus();
        taskOpinion.setOpinion(content);
        taskOpinion.setEndTime(new Date());
        //时间间隔计算
        Long duration = taskOpinion.getEndTime().getTime() - taskOpinion.getStartTime().getTime();
        taskOpinion.setDurTime(duration);
        taskOpinion.setCheckStatus(TaskOpinion.STATUS_CHANGE_ASIGNEE);
        taskOpinion.setExeUserId(curUser.getUserId());
        taskOpinion.setExeFullname(curUser.getFullName());
        taskOpinionService.update(taskOpinion);

        // 添加审批历史
        TaskOpinion newTaskOpinion = new TaskOpinion();
        newTaskOpinion.setTaskId(taskId);
        newTaskOpinion.setProcDefId(processRun.getProcDefId());
        newTaskOpinion.setProcInstId(processRun.getProcInstId());
        newTaskOpinion.setStartTime(new Date());
        newTaskOpinion.setTaskKey(taskEntity.getTaskDefinitionKey());
        newTaskOpinion.setOpinionId(UUIDUtils.getUUIDFor32());
        newTaskOpinion.setTaskName(taskEntity.getName());
        newTaskOpinion.setCheckStatus(checkStatus);

        //处理审批意见的父流程ID
        this.dealTaskOpinSupId(taskEntity, newTaskOpinion);

        taskOpinionDao.add(newTaskOpinion);

        List<SysUser> receiverUser = new ArrayList<>();
        receiverUser.add(user);
        //发送消息
        String title = "现将事项【" + processRun.getSubject() + "】指派给您,请注意查收";
        taskMessageService.sendMessage(curUser, receiverUser, informType, title, content);
        return "task.assignee.update.success";
    }

    /**
     * 保存沟通信息。
     *
     * <pre>
     *  1.如果任务这个任务执行人为空的情况，先将当前设置成任务执行人。
     *  2.产生沟通任务。
     *  3.添加意见。
     *  4.保存任务接收人。
     *  5.产生通知消息。
     * </pre>
     *
     * @param taskEntity 任务实例
     * @param opinion    意见
     * @param informType 通知类型
     * @param userIds    用户ID
     * @param subject    主题信息
     * @throws Exception 异常
     */
    public void saveCommunication(TaskEntity taskEntity, String opinion, String informType, String userIds, String subject) throws Exception {
        String taskId = taskEntity.getId();
        String[] aryUsers = userIds.split(",");

        SysUser sysUser = (SysUser) ContextUtil.getCurrentUser();

        // 任务执行人为空设定当前人为任务执行人。
        String assignee = taskEntity.getAssignee();
        if (ServiceUtil.isAssigneeEmpty(assignee)) {
            taskDao.updateTaskAssignee(taskId, sysUser.getUserId());
            // 根据任务ID获取意见。
/*            TaskOpinion oldOpinion = taskOpinionDao.getByTaskId(taskId);
            oldOpinion.setExeUserId(sysUser.getUserId());
            oldOpinion.setExeFullname(sysUser.getFullName());
            taskOpinionDao.update(oldOpinion);*/
        }

        // 产生沟通任务
        Map<String, String> usrIdTaskIds = bpmService.genCommunicationTask(taskEntity, aryUsers, sysUser);

        String opinionId = UUIDUtils.getUUIDFor32();

        //新增审批意见
        TaskOpinion taskOpinion = new TaskOpinion();
        taskOpinion.setOpinionId(opinionId);
        taskOpinion.setOpinion(opinion);
        taskOpinion.setProcDefId(taskEntity.getProcessDefinitionId());
        taskOpinion.setProcInstId(taskEntity.getProcessInstanceId());
        taskOpinion.setStartTime(new Date());
        taskOpinion.setEndTime(new Date());
        taskOpinion.setDurTime(0L);
        taskOpinion.setExeUserId(sysUser.getUserId());
        taskOpinion.setExeFullname(sysUser.getFullName());
        taskOpinion.setTaskKey(taskEntity.getTaskDefinitionKey());
        taskOpinion.setTaskName(taskEntity.getName());
        taskOpinion.setCheckStatus(TaskOpinion.STATUS_COMMUNICATION);
        // 处理审批意见的父流程ID
        this.dealTaskOpinSupId(taskEntity, taskOpinion);
        // 增加流程意见
        taskOpinionDao.add(taskOpinion);
        // 保存接收人
        commuReceiverService.saveReceiver(opinionId, usrIdTaskIds, sysUser);
        // 发送通知
        notifyCommu(subject, usrIdTaskIds, informType, sysUser, opinion, SysTemplate.USE_TYPE_COMMUNICATION);
    }

    /**
     * 发送短信或者邮件通知。
     *
     * @param subject      标题
     * @param usrIdTaskIds 目标任务用户与ID对应关系列表
     * @param informTypes  通知类型。
     * @param sysUser      发送用户
     * @param opinion      意见(或原因)
     * @param sysTemplate  消息模板类型
     * @throws Exception 异常
     */
    public void notifyCommu(String subject, Map<String, String> usrIdTaskIds, String informTypes, SysUser sysUser, String opinion, Integer sysTemplate) throws Exception {
        //获取沟通消息模板
        Map<String, String> msgTempMap = sysTemplateService.getTempByFun(sysTemplate);
        // 如果目标节点是空，获取目标节点
        if (usrIdTaskIds == null || usrIdTaskIds.isEmpty()) {
            return;
        }
        for (Map.Entry<String, String> entry : usrIdTaskIds.entrySet()) {
            String userId = entry.getKey();
            if (userId.equals(sysUser.getUserId())) {
                continue;
            }
            String taskId = entry.getValue();
            SysUser receiverUser = sysUserDao.getById(userId);
            List<SysUser> receiverUserList = new ArrayList<>(1);
            receiverUserList.add(receiverUser);
            taskMessageService.sendMessage(sysUser, receiverUserList, informTypes, msgTempMap, subject, opinion, taskId, null, null);
        }
    }

    /**
     * 处理流转操作。
     *
     * @param taskOpinion 审批意见
     * @param informType  消息类型
     * @param isAgree     是否同意
     * @param taskCmd     流程运行命令执行对象
     * @throws Exception 异常
     */
    @Transactional(rollbackFor = Exception.class)
    public void handTransTo(TaskOpinion taskOpinion, String informType, boolean isAgree, ProcessCmd taskCmd) throws Exception {

        TaskEntity taskEntity = bpmService.getTask(taskOpinion.getTaskId());
        ProcessRun processRun = getByProcInstanceId(taskEntity.getProcessInstanceId());
        taskCmd.setSubject(processRun.getSubject());
        taskOpinion.setOpinionId(UUIDUtils.getUUIDFor32());
        taskOpinion.setProcDefId(taskEntity.getProcessDefinitionId());
        taskOpinion.setProcInstId(taskEntity.getProcessInstanceId());
        taskOpinion.setStartTime(taskEntity.getCreateTime());
        taskOpinion.setEndTime(new Date());
        //计算时间差
        Long duration = taskOpinion.getEndTime().getTime() - taskOpinion.getStartTime().getTime();
        taskOpinion.setDurTime(duration);
        //获取当前用户
        SysUser sysUser = (SysUser) ContextUtil.getCurrentUser();
        taskOpinion.setExeUserId(sysUser.getUserId());
        taskOpinion.setExeFullname(sysUser.getFullName());
        taskOpinion.setTaskKey(taskEntity.getTaskDefinitionKey());
        taskOpinion.setTaskName(taskEntity.getName());


        String description = taskEntity.getDescription();
        Integer sysTemplateType = 0;
        if (description.equals(TaskOpinion.STATUS_TRANSTO.toString()) || description.equals(TaskOpinion.STATUS_TRANSTO_ING.toString())) {
            //加签反馈
            if (taskEntity.getAssignee().equals(sysUser.getUserId())) {
                taskOpinion.setCheckStatus(TaskOpinion.STATUS_REPLACE_SUBMIT);
            } else {
                taskOpinion.setCheckStatus(TaskOpinion.STATUS_INTERVENE);
                String opinion = taskOpinion.getOpinion();
                String userId = taskEntity.getAssignee();
                SysUser assignee = sysUserService.getOneById(userId);
                opinion += "(原执行人【" + assignee.getFullName() + "】)";
                taskOpinion.setOpinion(opinion);
            }
            sysTemplateType = SysTemplate.USE_TYPE_TRANSTO_FEEDBACK;
            taskCmd.setVoteAgree(isAgree ? TaskOpinion.STATUS_AGREE : TaskOpinion.STATUS_REFUSE);
        } else {
            // 沟通反馈
            taskOpinion.setCheckStatus(TaskOpinion.STATUS_NOTIFY);
            sysTemplateType = SysTemplate.USE_TYPE_FEEDBACK;
            taskCmd.setVoteAgree(TaskOpinion.STATUS_NOTIFY);
        }
        //添加用于流程统计的操作记录
        bpmPerformanceService.addProcessRecord(taskOpinion.getTaskId(), taskCmd);
        //向原执行人发送任务完成提醒消息
        Map<String, String> userIdTaskIds = new HashMap<>(1);
        ProcessTask parentTask = getByTaskId(taskEntity.getParentTaskId());
        userIdTaskIds.put(parentTask.getAssignee(), taskEntity.getParentTaskId());
        notifyCommu(processRun.getSubject(), userIdTaskIds, informType, sysUser, taskOpinion.getOpinion(), sysTemplateType);

        //处理表单
        FormData formData = processRunService.handlerFormData(taskCmd, processRun, taskEntity.getTaskDefinitionKey());
        if (formData != null) {
            Map<String, String> optionsMap = new HashMap<>(1);
            optionsMap.put("option", taskOpinion.getOpinion());
            // 记录意见
            updOption(optionsMap, taskCmd);
        }
        // 保存反馈信息 这里面会删除task的数据
        saveOpinion(taskEntity, taskOpinion);
        // 设置沟通人员或流转人员的状态为已反馈
        commuReceiverService.setCommuReceiverStatusToFeedBack(taskEntity, sysUser);

        // 添加已办历史
        addActivityHistory(taskEntity);

        // 判断是否流转任务
        if (description.equals(TaskOpinion.STATUS_TRANSTO.toString()) || description.equals(TaskOpinion.STATUS_TRANSTO_ING.toString())) {
            String parentTaskId = taskEntity.getParentTaskId();
            BpmProTransTo bpmProTransTo = bpmProTransToService.getByTaskId(parentTaskId);
            handlerTransTo(taskCmd, bpmProTransTo, sysUser, parentTaskId, isAgree);
        }

        String memo = "在:【" + processRun.getSubject() + "】,节点【" + taskEntity.getName() + "】,意见:" + taskOpinion.getOpinion();
        bpmRunLogService.addRunLog(processRun.getRunId(), BpmRunLog.OPERATOR_TYPE_ADDOPINION, memo);
    }

    /**
     * 处理流转任务
     *
     * @param taskCmd       流程命令执行对象
     * @param bpmProTransTo 流程流转对象
     * @param sysUser       用户
     * @param parentTaskId  父任务id
     * @param isAgree       是否同意
     * @throws Exception 异常
     */
    public void handlerTransTo(ProcessCmd taskCmd, BpmProTransTo bpmProTransTo, SysUser sysUser, String parentTaskId, boolean isAgree) throws Exception {

        String voteContent = taskCmd.getVoteContent();
        if (StringUtil.isEmpty(voteContent)) {
            voteContent = "【" + sysUser.getFullName() + "】代替发起流转人提交任务";
        }
        // 没有流转记录
        if (BeanUtils.isEmpty(bpmProTransTo)) {
            throw new Exception("存在流转任务，但流转状态表中无此记录！流转失败！");
        }

        if (bpmProTransTo.getTransType() == 1) {
            // 非会签
            if (bpmProTransTo.getAction() == 1) {
                // 返回
                // 更改父执行人状态
                this.updateTaskDescription(parentTaskId, 1);
                // 删除其余流转任务
                taskDao.delTransToTaskByParentTaskId(parentTaskId);
                // 删除流转状态
                bpmProTransToService.delById(bpmProTransTo.getId());
            } else {
                // 提交
                taskCmd.setTaskId(parentTaskId);
                taskCmd.setVoteAgree(isAgree ? TaskOpinion.STATUS_AGREE : TaskOpinion.STATUS_REFUSE);
                taskCmd.setVoteContent(voteContent);
                // 更改初始执行人状态为正常流转
                this.updateTaskDescription(parentTaskId, 2);
                // 删除其余流转任务
                taskDao.delTransToTaskByParentTaskId(parentTaskId);
                // 删除流转状态
                bpmProTransToService.delById(bpmProTransTo.getId());
                this.handlePostAction(taskCmd, sysUser);
            }
        } else {
            // 会签
            // 记录被流转人意见
            Integer transResult = bpmProTransTo.getTransResult();
            if (transResult == 1 && !isAgree) {
                bpmProTransTo.setTransResult(2);
                bpmProTransToService.update(bpmProTransTo);
                transResult = 2;
            }

            // 根据parentTaskId和description获取剩余流转任务
            List<ProcessTask> list = taskDao.getByParentTaskId(parentTaskId);
            // 未做完流转任务
            if (!list.isEmpty()) {
                return;
            }
            if (bpmProTransTo.getAction() == 1) {
                // 返回
                // 更改初始执行人状态
                this.updateTaskDescription(parentTaskId, 1);
                // 删除流转状态
                bpmProTransToService.delById(bpmProTransTo.getId());
            } else {
                // 提交
                taskCmd.setTaskId(parentTaskId);
                taskCmd.setVoteAgree(transResult == 1 ? TaskOpinion.STATUS_AGREE : TaskOpinion.STATUS_REFUSE);
                taskCmd.setVoteContent(voteContent);
                // 更改初始执行人状态为正常流转
                this.updateTaskDescription(parentTaskId, 2);
                // 删除流转状态
                bpmProTransToService.delById(bpmProTransTo.getId());
                this.handlePostAction(taskCmd, sysUser);
            }
        }
    }

    /**
     * 流转任务完成时，用于更新父任务的状态
     *
     * @param taskId 任务id
     * @param action 流转状态
     */
    public void updateTaskDescription(String taskId, Integer action) {
        ProcessTask task = taskDao.getByTaskId(taskId);
        String parentTaskId = task.getParentTaskId();
        Short description;
        if (StringUtil.isNotEmpty(parentTaskId)) {
            // 多级加签
            description = TaskOpinion.STATUS_TRANSTO;
        } else {
            description = action == 1 ? TaskOpinion.STATUS_CHECKING : TaskOpinion.STATUS_COMMON_TRANSTO;
        }
        taskDao.updateTaskDescription(description.toString(), taskId);
    }

    /**
     * 处理加签时的提交动作
     *
     * @param taskCmd 流程命令执行对象
     * @param sysUser 用户
     * @throws Exception 异常
     */
    private void handlePostAction(ProcessCmd taskCmd, SysUser sysUser) throws Exception {
        boolean isAgree = taskCmd.getVoteAgree() == 1;
        String taskId = taskCmd.getTaskId();
        TaskEntity parentTask = bpmService.getTask(taskId);
        String parentTaskId = parentTask.getParentTaskId();
        if (StringUtil.isNotEmpty(parentTaskId)) {
            taskService.deleteTask(taskId);
            // 删除被流转任务产生的沟通任务
            taskDao.delCommuTaskByParentTaskId(taskId);
            // 添加已办历史
            this.addActivityHistory(parentTask);

            BpmProTransTo bpmProTransTo = bpmProTransToService.getByTaskId(parentTaskId);
            this.handlerTransTo(taskCmd, bpmProTransTo, sysUser, parentTaskId, isAgree);
        } else {
            this.nextProcess(taskCmd);
        }
    }

    /**
     * 更新意见。
     *
     * <pre>
     * 1.首先根据任务id查询意见，应为意见在task创建时这个意见就被产生出来，所以能直接获取到该意见。
     * 2.获取意见，注意一个节点只允许一个意见填写，不能在同一个任务节点上同时允许两个意见框的填写，比如同时科员意见，局长意见等。
     * 3.更新意见。
     * </pre>
     *
     * @param optionsMap 意见集合
     * @param cmd        流程命令执行对象
     */
    private void updOption(Map<String, String> optionsMap, ProcessCmd cmd) {
        if (BeanUtils.isEmpty(optionsMap)) {
            return;
        }
        Set<String> set = optionsMap.keySet();
        String key = set.iterator().next();
        String value = optionsMap.get(key);
        cmd.setVoteFieldName(key);
        cmd.setVoteContent(value);
    }

    /**
     * 添加已办历史。
     *
     * <pre>
     * 	添加沟通或流转反馈任务到已办。
     * </pre>
     *
     * @param taskEnt 任务
     */
    private void addActivityHistory(TaskEntity taskEnt) throws Exception {
        Map<String, Object> ent = AppUtil.getImplInstance(HistoricActivityInstanceEntity.class);
        SysUser sysUser = (SysUser) ContextUtil.getCurrentUser();

        ent.put("id", UUIDUtils.getUUIDFor32());
        ent.put("activityId", taskEnt.getTaskDefinitionKey());

        ent.put("activityName", taskEnt.getName());
        ent.put("processInstanceId", taskEnt.getProcessInstanceId());
        ent.put("assignee", sysUser.getUserId());
        ent.put("processDefinitionId", taskEnt.getProcessDefinitionId());
        ent.put("startTime", taskEnt.getCreateTime());
        ent.put("endTime", new Date());
        ent.put("durationInMillis", System.currentTimeMillis() - taskEnt.getCreateTime().getTime());
        ent.put("executionId", "0");
        ent.put("activityType", "userTask");
        historyActivityDao.addMap(ent);
    }

    /**
     * 获取某人的流程实例列表。
     *
     * @param modelId        流程定义ID
     * @param creatorId      创建人
     * @param instanceAmount 流程数量
     * @return 流程运行列表
     */
    public List<ProcessRun> getRefList(String modelId, String creatorId, Integer instanceAmount, Integer type) {
        List<ProcessRun> list;
        // 我提交的流程
        if (type == 0) {
            list = processRunDao.getRefList(modelId, creatorId, instanceAmount);
        }
        // 抄送给我的流程
        else {
            list = processRunDao.getRefListCopyTo(modelId, creatorId, instanceAmount);
        }

        return list;
    }

    /**
     * 保存补签意见
     *
     * @param task        任务对象
     * @param opinion     意见
     * @param informType  消息类型
     * @param userTaskMap 用户任务集合
     * @param subject     流程标题
     */
    @Transactional(rollbackFor = Exception.class)
    public void saveAddSignOpinion(TaskEntityImpl task, String opinion, String informType, Map<String, String> userTaskMap, String subject) throws Exception {
        SysUser sysUser = (SysUser) ContextUtil.getCurrentUser();
        String opinionId = UUIDUtils.getUUIDFor32();

        TaskOpinion taskOpinion = new TaskOpinion();
        taskOpinion.setOpinionId(opinionId);
        taskOpinion.setOpinion(opinion);
        taskOpinion.setProcDefId(task.getProcessDefinitionId());
        taskOpinion.setProcInstId(task.getProcessInstanceId());
        taskOpinion.setStartTime(new Date());
        taskOpinion.setEndTime(new Date());
        taskOpinion.setExeUserId(sysUser.getUserId());
        taskOpinion.setExeFullname(sysUser.getFullName());
        taskOpinion.setTaskKey(task.getTaskDefinitionKey());
        taskOpinion.setTaskName(task.getName());
        taskOpinion.setCheckStatus(TaskOpinion.STATUS_RETROACTIVE);
        // 处理审批意见的父流程ID
        this.dealTaskOpinSupId(task, taskOpinion);

        // 增加流程意见
        taskOpinionDao.add(taskOpinion);
        // 保存接收人
        commuReceiverService.saveReceiver(opinionId, userTaskMap, sysUser);
        // 发送通知
        notifyCommu(subject, userTaskMap, informType, sysUser, opinion, SysTemplate.USE_TYPE_RETROACTIVE);
    }

    /**
     * 批量审批
     *
     * @param taskIds 任务id 使用逗号进行分割。
     * @param opinion 审批意见
     */
    @Transactional(rollbackFor = Exception.class)
    public void nextProcessBatch(String taskIds, String opinion) throws Exception {
        String[] aryTaskId = taskIds.split(",");
        for (String taskId : aryTaskId) {
            TaskEntity taskEntity = bpmService.getTask(taskId);
            ProcessRun processRun = this.getByProcInstanceId(taskEntity.getProcessInstanceId());
            String subject = processRun.getSubject();
            if (taskEntity.getExecutionId() == null && TaskOpinion.STATUS_COMMUNICATION.toString().equals(taskEntity.getDescription())) {
                MessageUtil.addMsg("<span class='red'>" + subject + ",为沟通任务!</span><br/>");
                continue;
            }
            if (taskEntity.getExecutionId() == null && TaskOpinion.STATUS_TRANSTO.toString().equals(taskEntity.getDescription())) {
                MessageUtil.addMsg("<span class='red'>" + subject + ",为流转任务!</span><br/>");
                continue;
            }

            ProcessCmd processCmd = new ProcessCmd();
            processCmd.setVoteAgree(TaskOpinion.STATUS_AGREE);
            processCmd.setVoteContent(opinion);
            processCmd.setTaskId(taskId);
            //添加批处理的标记
            processCmd.addTransientVar("batComplte", true);
            nextProcess(processCmd);
            MessageUtil.addMsg("<span class='green'>" + subject + ",审批成功!</span><br>");
        }
    }

    /**
     * 转发办结流程（我的办结）
     *
     * @param processRun    流程运行对象
     * @param targetUserIds 转发接收人
     * @param currUser      当前用户
     * @param informType    通知类型
     * @param suggestion    转发意见
     * @return 执行结果
     */
    public int saveForward(ProcessRun processRun, List<String> targetUserIds, SysUser currUser, String informType, String suggestion) throws Exception {
        String instanceId = processRun.getProcInstId();
        String procDefId = processRun.getProcDefId();
        ActDefModel actDefModel = actDefModelDao.getByProcDefId(procDefId);
        String currUid = currUser.getUserId();
        if (BeanUtils.isEmpty(actDefModel) || BeanUtils.isEmpty(targetUserIds)) {
            return 0;
        }

        List<SysUser> userList = new ArrayList<>();
        Map<String, String> userCopyIdMap = new HashMap<>(16);
        for (String userId : targetUserIds) {
            if (userId.equals(currUid)) {
                return -1;
            }
            SysUser destUser = sysUserService.getById(userId);
            ProcessCopyTo copyTo = new ProcessCopyTo();
            copyTo.setCopyId(UUIDUtils.getUUIDFor32());
            //2020-01-06 jyq添加，类型为转发
            copyTo.setCpType(ProcessCopyTo.CPTYPE_SEND);
            copyTo.setProcInstId(instanceId);
            copyTo.setCcTime(new Date());
            copyTo.setCcUId(userId);
            copyTo.setCcUName(destUser.getFullName());
            copyTo.setIsRead(ProcessCopyTo.NOT_READ);
            copyTo.setRunId(processRun.getRunId());
            copyTo.setSubject(processRun.getSubject());
            copyTo.setDefTypeId(actDefModel.getTypeId());
            copyTo.setCreateId(currUid);
            copyTo.setCreator(currUser.getFullName());
            processCopyToService.add(copyTo);

            userList.add(destUser);
            userCopyIdMap.put(destUser.getUserId(), copyTo.getCopyId());
        }
        Map<String, String> msgTempMap = sysTemplateService.getTempByFun(SysTemplate.USE_TYPE_FORWARD);
        String subject = processRun.getSubject();
        String runId = processRun.getRunId();
        taskMessageService.sendMessage(currUser, userList, informType, msgTempMap, subject, suggestion, null, runId, userCopyIdMap);
        return 1;
    }

    /**
     * 保存流转信息。
     *
     * <pre>
     *  1.如果这个任务执行人为空的情况，先将当前设置成任务执行人。
     *  2.保存流转状态
     *  3.产生流转任务。
     *  4.添加意见。
     *  5.保存任务接收人。
     *  6.产生通知消息。
     * </pre>
     *
     * @param taskEntity 任务实例
     * @param opinion    意见
     * @param informType 通知类型
     * @param userIds    用户ID
     * @param transType  流转类型
     * @param action     执行后动作
     * @throws Exception 异常
     */
    @Transactional(rollbackFor = Exception.class)
    public void saveTransTo(TaskEntity taskEntity, String opinion, String informType, String userIds, String transType, String action, ProcessRun processRun, ProcessCmd taskCmd) throws Exception {
        String taskId = taskEntity.getId();
        String procInstId = taskEntity.getProcessInstanceId();
        String[] aryUsers = userIds.split(",");

        SysUser sysUser = (SysUser) ContextUtil.getCurrentUser();

        // 任务执行人为空设定当前人为任务执行人。
        String assignee = taskEntity.getAssignee();
        if (ServiceUtil.isAssigneeEmpty(assignee)) {
            // 根据任务ID获取意见。
            TaskOpinion oldOpinion = taskOpinionDao.getByTaskId(taskId);
            oldOpinion.setExeFullname(sysUser.getFullName());
            oldOpinion.setExeUserId(sysUser.getUserId());
            taskOpinionDao.update(oldOpinion);
            taskDao.updateTaskAssignee(taskId, sysUser.getUserId());
        }
        // 修改初始任务状态
        taskDao.updateTaskDescription(TaskOpinion.STATUS_TRANSTO_ING.toString(), taskId);
        // 删除沟通任务
        taskDao.delCommuTaskByParentTaskId(taskId);
        // 保存流转状态
        String id = UUIDUtils.getUUIDFor32();
        BpmProTransTo bpmProTransTo = new BpmProTransTo();
        bpmProTransTo.setId(id);
        bpmProTransTo.setTaskId(taskId);
        bpmProTransTo.setTransType(Integer.valueOf(transType));
        bpmProTransTo.setAction(Integer.valueOf(action));
        bpmProTransTo.setCreateTime(new Date());
        bpmProTransTo.setProcInstId(procInstId);
        bpmProTransTo.setTransResult(1);
        bpmProTransTo.setAssignee(userIds);
        if (ServiceUtil.isAssigneeEmpty(assignee)) {
            bpmProTransTo.setCreateUserId(sysUser.getUserId());
        } else {
            bpmProTransTo.setCreateUserId(assignee);
        }
        bpmProTransToService.add(bpmProTransTo);

        // 处理多级加签
        handleCascadeTransToTask(taskEntity);

        // 产生流转任务
        Map<String, String> usrIdTaskIds = bpmService.genTransToTask(taskEntity, aryUsers);

        String opinionId = UUIDUtils.getUUIDFor32();
        TaskOpinion taskOpinion = new TaskOpinion();
        taskOpinion.setOpinionId(opinionId);
        taskOpinion.setOpinion(opinion);
        taskOpinion.setProcDefId(taskEntity.getProcessDefinitionId());
        taskOpinion.setProcInstId(procInstId);
        taskOpinion.setStartTime(new Date());
        taskOpinion.setEndTime(new Date());
        taskOpinion.setDurTime(0L);
        taskOpinion.setExeUserId(sysUser.getUserId());
        taskOpinion.setExeFullname(sysUser.getFullName());
        taskOpinion.setTaskKey(taskEntity.getTaskDefinitionKey());
        taskOpinion.setTaskName(taskEntity.getName());
        taskOpinion.setCheckStatus(TaskOpinion.STATUS_TRANSTO);

        // 处理审批意见的父流程ID
        this.dealTaskOpinSupId(taskEntity, taskOpinion);

        // 增加流程意见
        taskOpinionDao.add(taskOpinion);
        // 保存接收人
        commuReceiverService.saveReceiver(opinionId, usrIdTaskIds, sysUser);
        // 发送通知。
        notifyCommu(processRun.getSubject(), usrIdTaskIds, informType, sysUser, opinion, SysTemplate.USE_TYPE_TRANSTO);

        // 保存表单数据。
        processRunService.handlerFormData(taskCmd, processRun, taskEntity.getTaskDefinitionKey());

        //记录日志
        String runId = processRun.getRunId();
        String memo = "在:【" + processRun.getSubject() + "】,节点【" + taskEntity.getName() + "】,意见:" + opinion;
        bpmRunLogService.addRunLog(runId, BpmRunLog.OPERATOR_TYPE_ADDOPINION, memo);
    }

    /**
     * 处理多级加签 若父加签任务的加签类型为非会签，则删除其余流转任务，默认当前任务人为加签执行人
     *
     * @param taskEntity 任务对象
     */
    private void handleCascadeTransToTask(TaskEntity taskEntity) {
        String parentTaskId = taskEntity.getParentTaskId();
        if (StringUtil.isEmpty(parentTaskId)) {
            return;
        }

        BpmProTransTo parentTranTo = bpmProTransToService.getByTaskId(parentTaskId);
        // 会签 直接返回
        if (parentTranTo.getTransType() != 1) {
            return;
        }
        // 非会签的情况。
        List list = taskDao.queryByParentTaskIdAndDesc(parentTaskId, TaskOpinion.STATUS_TRANSTO.toString());
        for (Object aList : list) {
            ProcessTask task = (ProcessTask) aList;
            if (!task.getId().equals(taskEntity.getId())) {
                taskService.deleteTask(task.getId());
            }
        }
    }

    /**
     * 删除流程运行实例 BPM_PRO_RUN
     * 同时删除
     * 流程实例扩展    BPM_PRO_RUN_HIS、
     * 流程任务候选人  ACT_RU_IDENTITYLINK、
     * 流程任务        ACT_RU_TASK、
     * 流程变量        ACT_RU_VARIABLE、
     * 流程执行堆栈树  ACT_RU_EXECUTION、
     * 抄送转发        BPM_PRO_COPYTO、
     * 代理转办任务    BPM_TASK_EXE、
     * 通知任务接收人  BPM_COMMU_RECEIVER、
     * 任务是否已读    BPM_TASK_READ
     *
     * @param procDefId 流程定义id
     */
    public void delByProcDefId(String procDefId) {
        List<ProcessRun> list = processRunDao.queryByProcDefId(procDefId);
        List<BpmTaskExe> bpmTaskExeList;
        for (ProcessRun processRun : list) {
            /*if(ProcessRun.STATUS_FORM.equals(processRun.getStatus())){
                //如果状态为草稿状态（status==4）则跳过
                continue;
            }*/
            //删除BPM_PRO_COPYTO：抄送转发
            processCopyToDao.delByRunId(processRun.getRunId());
            //依据BPM_TASK_EXE：代理转办任务删除 BPM_COMMU_RECEIVER：通知任务接收人，BPM_TASK_READ：任务是否已读
            bpmTaskExeList = bpmTaskExeDao.queryByRunId(processRun.getRunId());
            for (BpmTaskExe bpmTaskExe : bpmTaskExeList) {
                commuReceiverDao.delByTaskId(bpmTaskExe.getTaskId());
                taskReadDao.delByProcInstId(bpmTaskExe.getProcInstId());
            }
            //删除BPM_TASK_EXE：代理转办任务
            bpmTaskExeDao.delByRunId(processRun.getRunId());
            //删除流程运行实体（ProcessRun），级联删除Act流程实例以及父流程
            //删除ACT_RU_VARIABLE：流程变量、ACT_RU_IDENTITYLINK：流程任务候选人、ACT_RU_TASK：流程任务、ACT_RU_EXECUTION：流程执行堆栈树、BPM_PRO_RUN：流程运行实例
            deleteProcessInstance(processRun);
        }
        //删除ACT_HI_PROCINST：历史流程实例
        processRunDao.delActHiProcessInstanceByProcDefId(procDefId);
        //删除BPM_PRO_RUN_HIS：流程实例扩展
        processRunDao.delHistoryByProcDefId(procDefId);
    }

    /**
     * 根据runId查询表单密级，离线表单暂且不考虑
     *
     * @param runId 运行Id
     * @return 表单密级
     * @throws Exception
     */
    public int getPrivacyLevelByRunId(String runId) throws Exception {
        ProcessRun processRun = processRunService.getById(runId);
        return this.getPrivacyLevelByProcessRun(processRun);
    }

    public int getPrivacyLevelByProcessRun(ProcessRun processRun) throws Exception {
        if (null != processRun && StringUtil.isNotEmpty(processRun.getTableName())) {
            //在线表单
            return formHandlerService.getPrivacyLevelByTable(processRun.getDsAlias(), processRun.getTableName(), processRun.getPkName(), processRun.getBusinessKey());
        }
        return 1;
    }
}
